about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2020-05-21 15:02:08 +0000
committerbors <bors@rust-lang.org>2020-05-21 15:02:08 +0000
commit148c125b1bf20674e8d0c55c21b3433a6d465b07 (patch)
tree95cad82e9b74b3e71100435b1060574bf6b8c522
parent06c9fef822b890054fcefa9a567b57eb6edfe638 (diff)
parenta114a237231586e754f8d6de2759e69ee9d90a2c (diff)
downloadrust-148c125b1bf20674e8d0c55c21b3433a6d465b07.tar.gz
rust-148c125b1bf20674e8d0c55c21b3433a6d465b07.zip
Auto merge of #71718 - NeoRaider:ffi_const_pure, r=Amanieu
Experimentally add `ffi_const` and `ffi_pure` extern fn attributes

Add FFI function attributes corresponding to clang/gcc/... `const` and `pure`.

Rebased version of #58327 by @gnzlbg with the following changes:

- Switched back from the `c_ffi_const` and `c_ffi_pure` naming to `ffi_const` and `ffi_pure`, as I agree with https://github.com/rust-lang/rust/pull/58327#issuecomment-462718772 and this nicely aligns with `ffi_returns_twice`
- (Hopefully) took care of all of @hanna-kruppe's change requests in the original PR

r? @hanna-kruppe
-rw-r--r--src/doc/unstable-book/src/language-features/ffi-const.md47
-rw-r--r--src/doc/unstable-book/src/language-features/ffi-pure.md51
-rw-r--r--src/librustc_codegen_llvm/attributes.rs6
-rw-r--r--src/librustc_error_codes/error_codes.rs3
-rw-r--r--src/librustc_feature/active.rs6
-rw-r--r--src/librustc_feature/builtin_attrs.rs2
-rw-r--r--src/librustc_middle/middle/codegen_fn_attrs.rs6
-rw-r--r--src/librustc_span/symbol.rs2
-rw-r--r--src/librustc_typeck/collect.rs37
-rw-r--r--src/test/codegen/ffi-const.rs12
-rw-r--r--src/test/codegen/ffi-pure.rs12
-rw-r--r--src/test/ui/feature-gates/feature-gate-ffi_const.rs6
-rw-r--r--src/test/ui/feature-gates/feature-gate-ffi_const.stderr12
-rw-r--r--src/test/ui/feature-gates/feature-gate-ffi_pure.rs6
-rw-r--r--src/test/ui/feature-gates/feature-gate-ffi_pure.stderr12
-rw-r--r--src/test/ui/ffi_const.rs5
-rw-r--r--src/test/ui/ffi_const.stderr8
-rw-r--r--src/test/ui/ffi_const2.rs11
-rw-r--r--src/test/ui/ffi_const2.stderr8
-rw-r--r--src/test/ui/ffi_pure.rs5
-rw-r--r--src/test/ui/ffi_pure.stderr8
21 files changed, 265 insertions, 0 deletions
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 00000000000..9a1ced4033b
--- /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 00000000000..7bfd7a378f0
--- /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
diff --git a/src/librustc_codegen_llvm/attributes.rs b/src/librustc_codegen_llvm/attributes.rs
index 64412843f6d..421c6aca1a9 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 d6863c615f6..5865042859d 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 30b8b52bf24..90b2380d864 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 466b318bca7..44971a98cc3 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 e3fe0b3111e..c480944069e 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 a38f5949204..6a6098710e8 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 fe3028102c6..66ef6a04be9 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) {
diff --git a/src/test/codegen/ffi-const.rs b/src/test/codegen/ffi-const.rs
new file mode 100644
index 00000000000..440d022a12c
--- /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 00000000000..f0ebc1caa09
--- /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 00000000000..27323b1b602
--- /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 00000000000..bed6a2ce488
--- /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 <https://github.com/rust-lang/rust/issues/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 00000000000..e24a686853c
--- /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 00000000000..2b0308fd661
--- /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 <https://github.com/rust-lang/rust/issues/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 00000000000..7aeb5a49a1b
--- /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 00000000000..623551cc07b
--- /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 00000000000..4bd9637f083
--- /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 00000000000..0b401942c47
--- /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 00000000000..c37d34c8784
--- /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 00000000000..3a849c0bca7
--- /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
+