From 5a08333890ca2cf58f80113e7b97ff4da7795244 Mon Sep 17 00:00:00 2001 From: David Wood Date: Tue, 17 Sep 2024 17:30:04 +0100 Subject: [PATCH 1/5] required inlining --- text/0000-required-inlining.md | 137 +++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 text/0000-required-inlining.md diff --git a/text/0000-required-inlining.md b/text/0000-required-inlining.md new file mode 100644 index 00000000000..ca9c349c415 --- /dev/null +++ b/text/0000-required-inlining.md @@ -0,0 +1,137 @@ +- Feature Name: `inline(required)` +- Start Date: 2024-09-17 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +rustc supports inlining functions with the `#[inline]`/`#[inline(always)]`/ +`#[inline(never)]` attributes, but these are only hints to the compiler. +Sometimes it is desirable to require inlining, not just suggest inlining: +security APIs can depend on being inlined for their security properties, and +performance intrinsics can work poorly if not inlined. + +# Motivation +[motivation]: #motivation + +rustc's current inlining attributes are only hints, they do not guarantee that +inlining is performed and users need to check manually whether inlining was +performed. While this is desirable for the vast majority of use cases, there are +circumstances where inlining is necessary to maintain the security properties of +an API, or where performance of intrinsics is likely to suffer if not inlined. +For example, consider: + +- Armv8.3-A's pointer authentication, when used explicitly with intrinsics[^1], +rely on being inlined to guarantee their security properties. Rust should make +it easier to guarantee that these intrinsics will be inlined and emit an error +if that is not possible or has not occurred. +- Arm's Neon and SVE performance intrinsics have work poorly if not inlined. +Since users rely on these intrinsics for their application's performance, Rust +should be able to warn users when these have not been inlined and performance +will not be as expected. + +[^1]: Armv8.3-A's pointer authentication can be used implicitly (automatic, +hint-based) and explicitly (intrinsics). Implicit pointer authentication is +already implemented by Rust. Explicit pointer authentication intrinsics are not +yet available in Rust. Exposing pointer authentication intrinsics would require +this RFC, or something providing equivalent guarantees, as a prerequisite. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +There are now be four variations of the `#[inline]` attribute - bare +`#[inline]`, `#[inline(always)]`, `#[inline(never)]` and `#[inline(required)]`. +Bare `#[inline]`, `#[inline(always)]` and `#[inline(never)]` are unchanged. +`#[inline(required)]` will always inline the annotated item, regardless of +optimization level. + +If it is not possible to inline the annotated item then the compiler will +emit a warn-by-default lint on the caller of the annotated item that the +item could not be inlined, providing a reason why inlining was not possible. +`#[inline(required)]` is an alias for `#[inline(required = "warn")]`. +`#[inline(required = "deny")]` can be used instead, which will emit a +deny-by-default lint - this variant can be used by items which must be inlined +to uphold security properties. Callers of annotated items can always override +this lint with the usual `#[allow]`/`#[warn]`/`#[deny]`/`#[expect]` attributes. +For example: + +```rust +// somewhere/in/std/intrinsics.rs +#[inline(required = "deny")] +pub unsafe fn ptrauth_auth_and_load_32(value: u64) -> u32 { + /* irrelevant detail */ +} + +// main.rs +fn do_ptrauth_warn() { + intrinsics::ptrauth_auth_and_load_32(/* ... */); + //~^ ERROR `ptrauth_auth_and_load_32` could not be inlined but requires inlining +} +``` + +Failures to force inline should not occur sporadically, users will only +encounter this lint when they call an annotated item from a location that the +inliner cannot inline into (e.g. a coroutine - a current limitation of the MIR +inliner), or when compiling with incompatible options (e.g. with code coverage - +a current limitation of the MIR inliner/code coverage implementations). + +`#[inline(required)]` differs from `#[inline(always)]` in that it emits the +lint when inlining does not happen and rustc will guarantee that no heuristics/ +optimization fuel considerations are employed to consider whether to inline +the item. + +It is intended that `#[inline(required)]` only be used in cases where inlining +is strictly necessary and is documented to be so, such as with some intrinsics. +`#[inline]`'s documentation should reflect that except in these cases, bare +`#[inline]`, `#[inline(always)]`, and `#[inline(never)]` should be preferred. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +As LLVM does not provide a mechanism to require inlining, only a mechanism to +provide hints as per the current `#[inline]` attributes, `#[inline(required)]` +will be implemented in rustc as part of the MIR inliner. Small modifications to +the MIR inliner will make the MIR pass run unconditionally (i.e. with `-O0`), +while only inlining non-`#[inline(required)]` items under the current conditions +and always inlining `#[inline(required)]` items. Any current limitations of the +MIR inliner will also apply to `#[inline(required)]` items and will be cases +where the lint is emitted. `#[inline(required)]` will be considered an alias of +`#[inline(always)]` after MIR inlining when performing codegen. + +# Drawbacks +[drawbacks]: #drawbacks + +- It may be undesirable for the MIR inliner to be necessary for the correctness +of a Rust program (i.e. for the `#[inline(required)]` case). + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +- Instead of `#[inline(required = "deny")]`, instead have `#[inline(required)]` +and `#[forced_inlining = "deny"]` or something like that - two separate +attributes. +- An "intrinsic macro" type of solution could be devised for the "security +properties" use case instead as macros are inherently inlined. +- Given the existence of security features with intrinsics that must be inlined +to guarantee their security properties, not doing this (or something else) +isn't a viable solution unless the project decides these are use cases that the +project does not wish to support. + +# Prior art +[prior-art]: #prior-art + +gcc and clang both have equivalents of `inline(always)` (e.g. [clang](https:// +clang.llvm.org/docs/AttributeReference.html#always-inline-force-inline)) +which ignore their heuristics but still only guarantee that they will attempt +inlining, and do not notify the user if inlining was not possible. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +There aren't any unresolved questions in this RFC currently. + +# Future possibilities +[future-possibilities]: #future-possibilities + +This feature is fairly self-contained and doesn't lend itself to having future expansion. From 53c42e122f707d2b71a1115c0b6c2f9a72ac07b5 Mon Sep 17 00:00:00 2001 From: David Wood Date: Tue, 24 Sep 2024 11:02:36 +0100 Subject: [PATCH 2/5] change from required to required/must --- text/0000-required-inlining.md | 83 ++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/text/0000-required-inlining.md b/text/0000-required-inlining.md index ca9c349c415..9b01aa280a9 100644 --- a/text/0000-required-inlining.md +++ b/text/0000-required-inlining.md @@ -1,4 +1,4 @@ -- Feature Name: `inline(required)` +- Feature Name: `inline(must)` and `inline(required)` - Start Date: 2024-09-17 - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) @@ -40,77 +40,82 @@ this RFC, or something providing equivalent guarantees, as a prerequisite. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -There are now be four variations of the `#[inline]` attribute - bare -`#[inline]`, `#[inline(always)]`, `#[inline(never)]` and `#[inline(required)]`. -Bare `#[inline]`, `#[inline(always)]` and `#[inline(never)]` are unchanged. -`#[inline(required)]` will always inline the annotated item, regardless of -optimization level. - -If it is not possible to inline the annotated item then the compiler will -emit a warn-by-default lint on the caller of the annotated item that the -item could not be inlined, providing a reason why inlining was not possible. -`#[inline(required)]` is an alias for `#[inline(required = "warn")]`. -`#[inline(required = "deny")]` can be used instead, which will emit a -deny-by-default lint - this variant can be used by items which must be inlined -to uphold security properties. Callers of annotated items can always override -this lint with the usual `#[allow]`/`#[warn]`/`#[deny]`/`#[expect]` attributes. -For example: +There are now be five variations of the `#[inline]` attribute, the +existing bare `#[inline]`, `#[inline(always)]`, `#[inline(never)]` and +the new `#[inline(must)]` and `#[inline(required)]`. Bare `#[inline]`, +`#[inline(always)]` and `#[inline(never)]` are unchanged. `#[inline(must)]` +and `#[inline(required)]` will always attempt to inline the annotated item, +regardless of optimization level. + +If it is not possible to inline the annotated item then the compiler will emit +a lint on the caller of the annotated item that the item could not be inlined, +providing a reason why inlining was not possible. `#[inline(must)]` emits a +warn-by-default lint and `#[inline(required)]` emits a deny-by-default lint, +which can be used by items which must be inlined to uphold security properties. +Callers of annotated items can always override this lint with the usual +`#[allow]`/`#[warn]`/`#[deny]`/`#[expect]` attributes. For example: ```rust // somewhere/in/std/intrinsics.rs -#[inline(required = "deny")] +#[inline(required)] pub unsafe fn ptrauth_auth_and_load_32(value: u64) -> u32 { /* irrelevant detail */ } // main.rs +#[warn(required_inline)] fn do_ptrauth_warn() { intrinsics::ptrauth_auth_and_load_32(/* ... */); - //~^ ERROR `ptrauth_auth_and_load_32` could not be inlined but requires inlining + //~^ WARN `ptrauth_auth_and_load_32` could not be inlined but requires inlining } ``` +Both `#[inline(must)]` and `#[inline(required)]` can optionally provide +the user a justification for why the annotated item is enforcing inlining, +such as `#[inline(must("maintain performance characteristics"))]` or +`#[inline(required("uphold security properties"))]`. + Failures to force inline should not occur sporadically, users will only encounter this lint when they call an annotated item from a location that the inliner cannot inline into (e.g. a coroutine - a current limitation of the MIR inliner), or when compiling with incompatible options (e.g. with code coverage - a current limitation of the MIR inliner/code coverage implementations). -`#[inline(required)]` differs from `#[inline(always)]` in that it emits the -lint when inlining does not happen and rustc will guarantee that no heuristics/ -optimization fuel considerations are employed to consider whether to inline -the item. +`#[inline(required)]` and `#[inline(must)]` differs from `#[inline(always)]` in +that it emits the lint when inlining does not happen and rustc will guarantee +that no heuristics/optimization fuel considerations are employed to consider +whether to inline the item. -It is intended that `#[inline(required)]` only be used in cases where inlining -is strictly necessary and is documented to be so, such as with some intrinsics. -`#[inline]`'s documentation should reflect that except in these cases, bare -`#[inline]`, `#[inline(always)]`, and `#[inline(never)]` should be preferred. +It is intended that `#[inline(required)]` and `#[inline(must)]` only be used +in cases where inlining is strictly necessary and is documented to be so, such +as with some intrinsics. `#[inline]`'s documentation should reflect that except +in these cases, bare `#[inline]`, `#[inline(always)]`, and `#[inline(never)]` +should be preferred. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -As LLVM does not provide a mechanism to require inlining, only a mechanism to -provide hints as per the current `#[inline]` attributes, `#[inline(required)]` -will be implemented in rustc as part of the MIR inliner. Small modifications to -the MIR inliner will make the MIR pass run unconditionally (i.e. with `-O0`), -while only inlining non-`#[inline(required)]` items under the current conditions -and always inlining `#[inline(required)]` items. Any current limitations of the -MIR inliner will also apply to `#[inline(required)]` items and will be cases -where the lint is emitted. `#[inline(required)]` will be considered an alias of -`#[inline(always)]` after MIR inlining when performing codegen. +As LLVM does not provide a mechanism to require inlining, only a mechanism +to provide hints as per the current `#[inline]` attributes, `#[inline(must)]` +and `#[inline(required)]` will be implemented in rustc as part of the MIR +inliner. Small modifications to the MIR inliner will make the MIR pass run +unconditionally (i.e. with `-O0`), while only inlining non-`#[inline(must)]`/ +`#[inline(required)]` items under the current conditions and always inlining +`#[inline(must)]`/`#[inline(required)]` items. Any current limitations of the +MIR inliner will also apply to `#[inline(must)]`/`#[inline(required)]` items and +will be cases where the lint is emitted. `#[inline(must)]`/`#[inline(required)]` +will be considered an alias of `#[inline(always)]` after MIR inlining when +performing codegen. # Drawbacks [drawbacks]: #drawbacks - It may be undesirable for the MIR inliner to be necessary for the correctness -of a Rust program (i.e. for the `#[inline(required)]` case). + of a Rust program (i.e. for the `#[inline(required)]` case). # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -- Instead of `#[inline(required = "deny")]`, instead have `#[inline(required)]` -and `#[forced_inlining = "deny"]` or something like that - two separate -attributes. - An "intrinsic macro" type of solution could be devised for the "security properties" use case instead as macros are inherently inlined. - Given the existence of security features with intrinsics that must be inlined From 124bc66d9ecaa5375377a762819aca999eccb416 Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 14 Oct 2024 10:57:09 +0100 Subject: [PATCH 3/5] updates from EuroRust discussions --- text/0000-required-inlining.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/text/0000-required-inlining.md b/text/0000-required-inlining.md index 9b01aa280a9..a6cf50e79c5 100644 --- a/text/0000-required-inlining.md +++ b/text/0000-required-inlining.md @@ -31,6 +31,9 @@ Since users rely on these intrinsics for their application's performance, Rust should be able to warn users when these have not been inlined and performance will not be as expected. +In general, for many intrinsics, inlining is not an optimisation but an +important part of the semantics, regardless of optimisation level. + [^1]: Armv8.3-A's pointer authentication can be used implicitly (automatic, hint-based) and explicitly (intrinsics). Implicit pointer authentication is already implemented by Rust. Explicit pointer authentication intrinsics are not @@ -86,11 +89,10 @@ that it emits the lint when inlining does not happen and rustc will guarantee that no heuristics/optimization fuel considerations are employed to consider whether to inline the item. -It is intended that `#[inline(required)]` and `#[inline(must)]` only be used -in cases where inlining is strictly necessary and is documented to be so, such -as with some intrinsics. `#[inline]`'s documentation should reflect that except -in these cases, bare `#[inline]`, `#[inline(always)]`, and `#[inline(never)]` -should be preferred. +`#[inline(must)]` and `#[inline(required)]` are intended to remain unstable +indefinitely and be used only within the standard library (e.g. on intrinsics). +This could be relaxed if there were sufficient motivation for use of these +inlining attributes in user code. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -126,10 +128,10 @@ project does not wish to support. # Prior art [prior-art]: #prior-art -gcc and clang both have equivalents of `inline(always)` (e.g. [clang](https:// +gcc and clang both have partial equivalents (e.g. [clang](https:// clang.llvm.org/docs/AttributeReference.html#always-inline-force-inline)) -which ignore their heuristics but still only guarantee that they will attempt -inlining, and do not notify the user if inlining was not possible. +which ignore their inlining heuristics but still only guarantee that they +will attempt inlining, and do not notify the user if inlining was not possible. # Unresolved questions [unresolved-questions]: #unresolved-questions From 91ca8c49e2e0680b1356be82626774316c5568f3 Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 14 Oct 2024 15:45:00 +0100 Subject: [PATCH 4/5] rename to PR number --- text/{0000-required-inlining.md => 3711-required-inlining.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename text/{0000-required-inlining.md => 3711-required-inlining.md} (98%) diff --git a/text/0000-required-inlining.md b/text/3711-required-inlining.md similarity index 98% rename from text/0000-required-inlining.md rename to text/3711-required-inlining.md index a6cf50e79c5..ad9f503a7b5 100644 --- a/text/0000-required-inlining.md +++ b/text/3711-required-inlining.md @@ -1,6 +1,6 @@ - Feature Name: `inline(must)` and `inline(required)` - Start Date: 2024-09-17 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- RFC PR: [rust-lang/rfcs#3711](https://github.com/rust-lang/rfcs/pull/3711) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary From c0e00a829a02f8d6ee7bdef2dc09c9d9396e6ca0 Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 14 Oct 2024 16:34:46 +0100 Subject: [PATCH 5/5] review feedback updates --- text/3711-required-inlining.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/text/3711-required-inlining.md b/text/3711-required-inlining.md index ad9f503a7b5..f4f6d44fcf2 100644 --- a/text/3711-required-inlining.md +++ b/text/3711-required-inlining.md @@ -91,7 +91,11 @@ whether to inline the item. `#[inline(must)]` and `#[inline(required)]` are intended to remain unstable indefinitely and be used only within the standard library (e.g. on intrinsics). -This could be relaxed if there were sufficient motivation for use of these +This is to avoid misuse of the attribute which ultimately harms performance +of Rust programs. As these attributes can be used in the standard library +on the relevant intrinsics which require inlining for performance or security +reasons, they will still be useful despite being indefinitely unstable. These +could be stabilized if there were sufficient motivation for use of these inlining attributes in user code. # Reference-level explanation @@ -124,6 +128,11 @@ properties" use case instead as macros are inherently inlined. to guarantee their security properties, not doing this (or something else) isn't a viable solution unless the project decides these are use cases that the project does not wish to support. +- Instead of having two attributes and two lints, merge both attributes and lints +and require users use `#[deny(..)]` when calling those intrinsics which must be +inlined to uphold security properties. This RFC prefers to have two lints and +attributes so that the annotation on the intrinsic decides whether it is +error-worthy for it to not be inlined. # Prior art [prior-art]: #prior-art