From abc236414b4bd609513899e651c41f314f71bac4 Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Tue, 14 Apr 2020 00:19:46 +0200 Subject: [PATCH 1/3] Implement `#[ffi_const]` and `#[ffi_pure]` function attributes Introduce function attribute corresponding to the `const`/`pure` attributes supported by GCC, clang and other compilers. Based on the work of gnzlbg . --- src/librustc_codegen_llvm/attributes.rs | 6 +++ src/librustc_error_codes/error_codes.rs | 3 ++ src/librustc_feature/active.rs | 6 +++ src/librustc_feature/builtin_attrs.rs | 2 + .../middle/codegen_fn_attrs.rs | 6 +++ src/librustc_span/symbol.rs | 2 + src/librustc_typeck/collect.rs | 37 +++++++++++++++++++ 7 files changed, 62 insertions(+) diff --git a/src/librustc_codegen_llvm/attributes.rs b/src/librustc_codegen_llvm/attributes.rs index 64412843f6def..421c6aca1a978 100644 --- a/src/librustc_codegen_llvm/attributes.rs +++ b/src/librustc_codegen_llvm/attributes.rs @@ -284,6 +284,12 @@ pub fn from_fn_attrs(cx: &CodegenCx<'ll, 'tcx>, llfn: &'ll Value, instance: ty:: if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::FFI_RETURNS_TWICE) { Attribute::ReturnsTwice.apply_llfn(Function, llfn); } + if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::FFI_PURE) { + Attribute::ReadOnly.apply_llfn(Function, llfn); + } + if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::FFI_CONST) { + Attribute::ReadNone.apply_llfn(Function, llfn); + } if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NAKED) { naked(llfn, true); } diff --git a/src/librustc_error_codes/error_codes.rs b/src/librustc_error_codes/error_codes.rs index d6863c615f644..5865042859dca 100644 --- a/src/librustc_error_codes/error_codes.rs +++ b/src/librustc_error_codes/error_codes.rs @@ -616,4 +616,7 @@ E0754: include_str!("./error_codes/E0754.md"), E0724, // `#[ffi_returns_twice]` is only allowed in foreign functions E0726, // non-explicit (not `'_`) elided lifetime in unsupported position // E0738, // Removed; errored on `#[track_caller] fn`s in `extern "Rust" { ... }`. + E0755, // `#[ffi_pure]` is only allowed on foreign functions + E0756, // `#[ffi_const]` is only allowed on foreign functions + E0757, // `#[ffi_const]` functions cannot be `#[ffi_pure]` } diff --git a/src/librustc_feature/active.rs b/src/librustc_feature/active.rs index 30b8b52bf24d0..90b2380d86450 100644 --- a/src/librustc_feature/active.rs +++ b/src/librustc_feature/active.rs @@ -565,6 +565,12 @@ declare_features! ( /// Allow conditional compilation depending on rust version (active, cfg_version, "1.45.0", Some(64796), None), + /// Allows the use of `#[ffi_pure]` on foreign functions. + (active, ffi_pure, "1.45.0", Some(58329), None), + + /// Allows the use of `#[ffi_const]` on foreign functions. + (active, ffi_const, "1.45.0", Some(58328), None), + // ------------------------------------------------------------------------- // feature-group-end: actual feature gates // ------------------------------------------------------------------------- diff --git a/src/librustc_feature/builtin_attrs.rs b/src/librustc_feature/builtin_attrs.rs index 466b318bca730..44971a98cc32f 100644 --- a/src/librustc_feature/builtin_attrs.rs +++ b/src/librustc_feature/builtin_attrs.rs @@ -331,6 +331,8 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ), gated!(ffi_returns_twice, Whitelisted, template!(Word), experimental!(ffi_returns_twice)), + gated!(ffi_pure, Whitelisted, template!(Word), experimental!(ffi_pure)), + gated!(ffi_const, Whitelisted, template!(Word), experimental!(ffi_const)), gated!(track_caller, Whitelisted, template!(Word), experimental!(track_caller)), gated!( register_attr, CrateLevel, template!(List: "attr1, attr2, ..."), diff --git a/src/librustc_middle/middle/codegen_fn_attrs.rs b/src/librustc_middle/middle/codegen_fn_attrs.rs index e3fe0b3111e31..c480944069efb 100644 --- a/src/librustc_middle/middle/codegen_fn_attrs.rs +++ b/src/librustc_middle/middle/codegen_fn_attrs.rs @@ -77,6 +77,12 @@ bitflags! { const NO_SANITIZE_THREAD = 1 << 14; /// All `#[no_sanitize(...)]` attributes. const NO_SANITIZE_ANY = Self::NO_SANITIZE_ADDRESS.bits | Self::NO_SANITIZE_MEMORY.bits | Self::NO_SANITIZE_THREAD.bits; + /// #[ffi_pure]: applies clang's `pure` attribute to a foreign function + /// declaration. + const FFI_PURE = 1 << 15; + /// #[ffi_const]: applies clang's `const` attribute to a foreign function + /// declaration. + const FFI_CONST = 1 << 16; } } diff --git a/src/librustc_span/symbol.rs b/src/librustc_span/symbol.rs index a38f594920410..6a6098710e828 100644 --- a/src/librustc_span/symbol.rs +++ b/src/librustc_span/symbol.rs @@ -322,6 +322,8 @@ symbols! { f32, f64, feature, + ffi_const, + ffi_pure, ffi_returns_twice, field, field_init_shorthand, diff --git a/src/librustc_typeck/collect.rs b/src/librustc_typeck/collect.rs index fe3028102c685..66ef6a04be914 100644 --- a/src/librustc_typeck/collect.rs +++ b/src/librustc_typeck/collect.rs @@ -2374,6 +2374,43 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs { ) .emit(); } + } else if attr.check_name(sym::ffi_pure) { + if tcx.is_foreign_item(id) { + if attrs.iter().any(|a| a.check_name(sym::ffi_const)) { + // `#[ffi_const]` functions cannot be `#[ffi_pure]` + struct_span_err!( + tcx.sess, + attr.span, + E0757, + "`#[ffi_const]` function cannot be `#[ffi_pure]`" + ) + .emit(); + } else { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::FFI_PURE; + } + } else { + // `#[ffi_pure]` is only allowed on foreign functions + struct_span_err!( + tcx.sess, + attr.span, + E0755, + "`#[ffi_pure]` may only be used on foreign functions" + ) + .emit(); + } + } else if attr.check_name(sym::ffi_const) { + if tcx.is_foreign_item(id) { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::FFI_CONST; + } else { + // `#[ffi_const]` is only allowed on foreign functions + struct_span_err!( + tcx.sess, + attr.span, + E0756, + "`#[ffi_const]` may only be used on foreign functions" + ) + .emit(); + } } else if attr.check_name(sym::rustc_allocator_nounwind) { codegen_fn_attrs.flags |= CodegenFnAttrFlags::RUSTC_ALLOCATOR_NOUNWIND; } else if attr.check_name(sym::naked) { From a7d7f0bbe962811d2e5207762aa92c2059c33d1a Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Tue, 14 Apr 2020 00:21:19 +0200 Subject: [PATCH 2/3] Add tests for `#[ffi_const]` and `#[ffi_pure]` function attributes Based on the work of gnzlbg . --- src/test/codegen/ffi-const.rs | 12 ++++++++++++ src/test/codegen/ffi-pure.rs | 12 ++++++++++++ src/test/ui/feature-gates/feature-gate-ffi_const.rs | 6 ++++++ .../ui/feature-gates/feature-gate-ffi_const.stderr | 12 ++++++++++++ src/test/ui/feature-gates/feature-gate-ffi_pure.rs | 6 ++++++ .../ui/feature-gates/feature-gate-ffi_pure.stderr | 12 ++++++++++++ src/test/ui/ffi_const.rs | 5 +++++ src/test/ui/ffi_const.stderr | 8 ++++++++ src/test/ui/ffi_const2.rs | 11 +++++++++++ src/test/ui/ffi_const2.stderr | 8 ++++++++ src/test/ui/ffi_pure.rs | 5 +++++ src/test/ui/ffi_pure.stderr | 8 ++++++++ 12 files changed, 105 insertions(+) create mode 100644 src/test/codegen/ffi-const.rs create mode 100644 src/test/codegen/ffi-pure.rs create mode 100644 src/test/ui/feature-gates/feature-gate-ffi_const.rs create mode 100644 src/test/ui/feature-gates/feature-gate-ffi_const.stderr create mode 100644 src/test/ui/feature-gates/feature-gate-ffi_pure.rs create mode 100644 src/test/ui/feature-gates/feature-gate-ffi_pure.stderr create mode 100644 src/test/ui/ffi_const.rs create mode 100644 src/test/ui/ffi_const.stderr create mode 100644 src/test/ui/ffi_const2.rs create mode 100644 src/test/ui/ffi_const2.stderr create mode 100644 src/test/ui/ffi_pure.rs create mode 100644 src/test/ui/ffi_pure.stderr diff --git a/src/test/codegen/ffi-const.rs b/src/test/codegen/ffi-const.rs new file mode 100644 index 0000000000000..440d022a12cba --- /dev/null +++ b/src/test/codegen/ffi-const.rs @@ -0,0 +1,12 @@ +// compile-flags: -C no-prepopulate-passes +#![crate_type = "lib"] +#![feature(ffi_const)] + +pub fn bar() { unsafe { foo() } } + +extern { + // CHECK-LABEL: declare void @foo() + // CHECK-SAME: [[ATTRS:#[0-9]+]] + // CHECK-DAG: attributes [[ATTRS]] = { {{.*}}readnone{{.*}} } + #[ffi_const] pub fn foo(); +} diff --git a/src/test/codegen/ffi-pure.rs b/src/test/codegen/ffi-pure.rs new file mode 100644 index 0000000000000..f0ebc1caa09bd --- /dev/null +++ b/src/test/codegen/ffi-pure.rs @@ -0,0 +1,12 @@ +// compile-flags: -C no-prepopulate-passes +#![crate_type = "lib"] +#![feature(ffi_pure)] + +pub fn bar() { unsafe { foo() } } + +extern { + // CHECK-LABEL: declare void @foo() + // CHECK-SAME: [[ATTRS:#[0-9]+]] + // CHECK-DAG: attributes [[ATTRS]] = { {{.*}}readonly{{.*}} } + #[ffi_pure] pub fn foo(); +} diff --git a/src/test/ui/feature-gates/feature-gate-ffi_const.rs b/src/test/ui/feature-gates/feature-gate-ffi_const.rs new file mode 100644 index 0000000000000..27323b1b60280 --- /dev/null +++ b/src/test/ui/feature-gates/feature-gate-ffi_const.rs @@ -0,0 +1,6 @@ +#![crate_type = "lib"] + +extern { + #[ffi_const] //~ ERROR the `#[ffi_const]` attribute is an experimental feature + pub fn foo(); +} diff --git a/src/test/ui/feature-gates/feature-gate-ffi_const.stderr b/src/test/ui/feature-gates/feature-gate-ffi_const.stderr new file mode 100644 index 0000000000000..bed6a2ce48825 --- /dev/null +++ b/src/test/ui/feature-gates/feature-gate-ffi_const.stderr @@ -0,0 +1,12 @@ +error[E0658]: the `#[ffi_const]` attribute is an experimental feature + --> $DIR/feature-gate-ffi_const.rs:4:5 + | +LL | #[ffi_const] + | ^^^^^^^^^^^^ + | + = note: see issue #58328 for more information + = help: add `#![feature(ffi_const)]` to the crate attributes to enable + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/feature-gates/feature-gate-ffi_pure.rs b/src/test/ui/feature-gates/feature-gate-ffi_pure.rs new file mode 100644 index 0000000000000..e24a686853c88 --- /dev/null +++ b/src/test/ui/feature-gates/feature-gate-ffi_pure.rs @@ -0,0 +1,6 @@ +#![crate_type = "lib"] + +extern { + #[ffi_pure] //~ ERROR the `#[ffi_pure]` attribute is an experimental feature + pub fn foo(); +} diff --git a/src/test/ui/feature-gates/feature-gate-ffi_pure.stderr b/src/test/ui/feature-gates/feature-gate-ffi_pure.stderr new file mode 100644 index 0000000000000..2b0308fd661c2 --- /dev/null +++ b/src/test/ui/feature-gates/feature-gate-ffi_pure.stderr @@ -0,0 +1,12 @@ +error[E0658]: the `#[ffi_pure]` attribute is an experimental feature + --> $DIR/feature-gate-ffi_pure.rs:4:5 + | +LL | #[ffi_pure] + | ^^^^^^^^^^^ + | + = note: see issue #58329 for more information + = help: add `#![feature(ffi_pure)]` to the crate attributes to enable + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/ffi_const.rs b/src/test/ui/ffi_const.rs new file mode 100644 index 0000000000000..7aeb5a49a1b58 --- /dev/null +++ b/src/test/ui/ffi_const.rs @@ -0,0 +1,5 @@ +#![feature(ffi_const)] +#![crate_type = "lib"] + +#[ffi_const] //~ ERROR `#[ffi_const]` may only be used on foreign functions +pub fn foo() {} diff --git a/src/test/ui/ffi_const.stderr b/src/test/ui/ffi_const.stderr new file mode 100644 index 0000000000000..623551cc07bbb --- /dev/null +++ b/src/test/ui/ffi_const.stderr @@ -0,0 +1,8 @@ +error[E0756]: `#[ffi_const]` may only be used on foreign functions + --> $DIR/ffi_const.rs:4:1 + | +LL | #[ffi_const] + | ^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/test/ui/ffi_const2.rs b/src/test/ui/ffi_const2.rs new file mode 100644 index 0000000000000..4bd9637f0832c --- /dev/null +++ b/src/test/ui/ffi_const2.rs @@ -0,0 +1,11 @@ +#![feature(ffi_const, ffi_pure)] + +extern { + #[ffi_pure] //~ ERROR `#[ffi_const]` function cannot be `#[ffi_pure]` + #[ffi_const] + pub fn baz(); +} + +fn main() { + unsafe { baz() }; +} diff --git a/src/test/ui/ffi_const2.stderr b/src/test/ui/ffi_const2.stderr new file mode 100644 index 0000000000000..0b401942c4792 --- /dev/null +++ b/src/test/ui/ffi_const2.stderr @@ -0,0 +1,8 @@ +error[E0757]: `#[ffi_const]` function cannot be `#[ffi_pure]` + --> $DIR/ffi_const2.rs:4:5 + | +LL | #[ffi_pure] + | ^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/test/ui/ffi_pure.rs b/src/test/ui/ffi_pure.rs new file mode 100644 index 0000000000000..c37d34c8784bb --- /dev/null +++ b/src/test/ui/ffi_pure.rs @@ -0,0 +1,5 @@ +#![feature(ffi_pure)] +#![crate_type = "lib"] + +#[ffi_pure] //~ ERROR `#[ffi_pure]` may only be used on foreign functions +pub fn foo() {} diff --git a/src/test/ui/ffi_pure.stderr b/src/test/ui/ffi_pure.stderr new file mode 100644 index 0000000000000..3a849c0bca79c --- /dev/null +++ b/src/test/ui/ffi_pure.stderr @@ -0,0 +1,8 @@ +error[E0755]: `#[ffi_pure]` may only be used on foreign functions + --> $DIR/ffi_pure.rs:4:1 + | +LL | #[ffi_pure] + | ^^^^^^^^^^^ + +error: aborting due to previous error + From a114a237231586e754f8d6de2759e69ee9d90a2c Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Tue, 14 Apr 2020 00:21:27 +0200 Subject: [PATCH 3/3] Document `#[ffi_const]` and `#[ffi_pure]` function attributes in unstable book Based on the work of gnzlbg . --- .../src/language-features/ffi-const.md | 47 +++++++++++++++++ .../src/language-features/ffi-pure.md | 51 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 src/doc/unstable-book/src/language-features/ffi-const.md create mode 100644 src/doc/unstable-book/src/language-features/ffi-pure.md diff --git a/src/doc/unstable-book/src/language-features/ffi-const.md b/src/doc/unstable-book/src/language-features/ffi-const.md new file mode 100644 index 0000000000000..9a1ced4033b22 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/ffi-const.md @@ -0,0 +1,47 @@ +# `ffi_const` + +The `#[ffi_const]` attribute applies clang's `const` attribute to foreign +functions declarations. + +That is, `#[ffi_const]` functions shall have no effects except for its return +value, which can only depend on the values of the function parameters, and is +not affected by changes to the observable state of the program. + +Applying the `#[ffi_const]` attribute to a function that violates these +requirements is undefined behaviour. + +This attribute enables Rust to perform common optimizations, like sub-expression +elimination, and it can avoid emitting some calls in repeated invocations of the +function with the same argument values regardless of other operations being +performed in between these functions calls (as opposed to `#[ffi_pure]` +functions). + +## Pitfalls + +A `#[ffi_const]` function can only read global memory that would not affect +its return value for the whole execution of the program (e.g. immutable global +memory). `#[ffi_const]` functions are referentially-transparent and therefore +more strict than `#[ffi_pure]` functions. + +A common pitfall involves applying the `#[ffi_const]` attribute to a +function that reads memory through pointer arguments which do not necessarily +point to immutable global memory. + +A `#[ffi_const]` function that returns unit has no effect on the abstract +machine's state, and a `#[ffi_const]` function cannot be `#[ffi_pure]`. + +A `#[ffi_const]` function must not diverge, neither via a side effect (e.g. a +call to `abort`) nor by infinite loops. + +When translating C headers to Rust FFI, it is worth verifying for which targets +the `const` attribute is enabled in those headers, and using the appropriate +`cfg` macros in the Rust side to match those definitions. While the semantics of +`const` are implemented identically by many C and C++ compilers, e.g., clang, +[GCC], [ARM C/C++ compiler], [IBM ILE C/C++], etc. they are not necessarily +implemented in this way on all of them. It is therefore also worth verifying +that the semantics of the C toolchain used to compile the binary being linked +against are compatible with those of the `#[ffi_const]`. + +[ARM C/C++ compiler]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/Cacgigch.html +[GCC]: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-const-function-attribute +[IBM ILE C/C++]: https://www.ibm.com/support/knowledgecenter/fr/ssw_ibm_i_71/rzarg/fn_attrib_const.htm diff --git a/src/doc/unstable-book/src/language-features/ffi-pure.md b/src/doc/unstable-book/src/language-features/ffi-pure.md new file mode 100644 index 0000000000000..7bfd7a378f00b --- /dev/null +++ b/src/doc/unstable-book/src/language-features/ffi-pure.md @@ -0,0 +1,51 @@ +# `ffi_pure` + +The `#[ffi_pure]` attribute applies clang's `pure` attribute to foreign +functions declarations. + +That is, `#[ffi_pure]` functions shall have no effects except for its return +value, which shall not change across two consecutive function calls with +the same parameters. + +Applying the `#[ffi_pure]` attribute to a function that violates these +requirements is undefined behavior. + +This attribute enables Rust to perform common optimizations, like sub-expression +elimination and loop optimizations. Some common examples of pure functions are +`strlen` or `memcmp`. + +These optimizations are only applicable when the compiler can prove that no +program state observable by the `#[ffi_pure]` function has changed between calls +of the function, which could alter the result. See also the `#[ffi_const]` +attribute, which provides stronger guarantees regarding the allowable behavior +of a function, enabling further optimization. + +## Pitfalls + +A `#[ffi_pure]` function can read global memory through the function +parameters (e.g. pointers), globals, etc. `#[ffi_pure]` functions are not +referentially-transparent, and are therefore more relaxed than `#[ffi_const]` +functions. + +However, accesing global memory through volatile or atomic reads can violate the +requirement that two consecutive function calls shall return the same value. + +A `pure` function that returns unit has no effect on the abstract machine's +state. + +A `#[ffi_pure]` function must not diverge, neither via a side effect (e.g. a +call to `abort`) nor by infinite loops. + +When translating C headers to Rust FFI, it is worth verifying for which targets +the `pure` attribute is enabled in those headers, and using the appropriate +`cfg` macros in the Rust side to match those definitions. While the semantics of +`pure` are implemented identically by many C and C++ compilers, e.g., clang, +[GCC], [ARM C/C++ compiler], [IBM ILE C/C++], etc. they are not necessarily +implemented in this way on all of them. It is therefore also worth verifying +that the semantics of the C toolchain used to compile the binary being linked +against are compatible with those of the `#[ffi_pure]`. + + +[ARM C/C++ compiler]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/Cacigdac.html +[GCC]: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-pure-function-attribute +[IBM ILE C/C++]: https://www.ibm.com/support/knowledgecenter/fr/ssw_ibm_i_71/rzarg/fn_attrib_pure.htm