Skip to content

Commit

Permalink
Rollup merge of rust-lang#71718 - NeoRaider:ffi_const_pure, r=Amanieu
Browse files Browse the repository at this point in the history
Experimentally add `ffi_const` and `ffi_pure` extern fn attributes

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

Rebased version of rust-lang#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 rust-lang#58327 (comment) 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
  • Loading branch information
RalfJung authored May 21, 2020
2 parents 7a46e28 + a114a23 commit e89a99d
Show file tree
Hide file tree
Showing 21 changed files with 265 additions and 0 deletions.
47 changes: 47 additions & 0 deletions src/doc/unstable-book/src/language-features/ffi-const.md
Original file line number Diff line number Diff line change
@@ -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
51 changes: 51 additions & 0 deletions src/doc/unstable-book/src/language-features/ffi-pure.md
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions src/librustc_codegen_llvm/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
3 changes: 3 additions & 0 deletions src/librustc_error_codes/error_codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]`
}
6 changes: 6 additions & 0 deletions src/librustc_feature/active.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
// -------------------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions src/librustc_feature/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, ..."),
Expand Down
6 changes: 6 additions & 0 deletions src/librustc_middle/middle/codegen_fn_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/librustc_span/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ symbols! {
f32,
f64,
feature,
ffi_const,
ffi_pure,
ffi_returns_twice,
field,
field_init_shorthand,
Expand Down
37 changes: 37 additions & 0 deletions src/librustc_typeck/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
12 changes: 12 additions & 0 deletions src/test/codegen/ffi-const.rs
Original file line number Diff line number Diff line change
@@ -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();
}
12 changes: 12 additions & 0 deletions src/test/codegen/ffi-pure.rs
Original file line number Diff line number Diff line change
@@ -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();
}
6 changes: 6 additions & 0 deletions src/test/ui/feature-gates/feature-gate-ffi_const.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#![crate_type = "lib"]

extern {
#[ffi_const] //~ ERROR the `#[ffi_const]` attribute is an experimental feature
pub fn foo();
}
12 changes: 12 additions & 0 deletions src/test/ui/feature-gates/feature-gate-ffi_const.stderr
Original file line number Diff line number Diff line change
@@ -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`.
6 changes: 6 additions & 0 deletions src/test/ui/feature-gates/feature-gate-ffi_pure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#![crate_type = "lib"]

extern {
#[ffi_pure] //~ ERROR the `#[ffi_pure]` attribute is an experimental feature
pub fn foo();
}
12 changes: 12 additions & 0 deletions src/test/ui/feature-gates/feature-gate-ffi_pure.stderr
Original file line number Diff line number Diff line change
@@ -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`.
5 changes: 5 additions & 0 deletions src/test/ui/ffi_const.rs
Original file line number Diff line number Diff line change
@@ -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() {}
8 changes: 8 additions & 0 deletions src/test/ui/ffi_const.stderr
Original file line number Diff line number Diff line change
@@ -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

11 changes: 11 additions & 0 deletions src/test/ui/ffi_const2.rs
Original file line number Diff line number Diff line change
@@ -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() };
}
8 changes: 8 additions & 0 deletions src/test/ui/ffi_const2.stderr
Original file line number Diff line number Diff line change
@@ -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

5 changes: 5 additions & 0 deletions src/test/ui/ffi_pure.rs
Original file line number Diff line number Diff line change
@@ -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() {}
8 changes: 8 additions & 0 deletions src/test/ui/ffi_pure.stderr
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit e89a99d

Please sign in to comment.