Skip to content

Commit e89a99d

Browse files
authored
Rollup merge of rust-lang#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 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
2 parents 7a46e28 + a114a23 commit e89a99d

File tree

21 files changed

+265
-0
lines changed

21 files changed

+265
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# `ffi_const`
2+
3+
The `#[ffi_const]` attribute applies clang's `const` attribute to foreign
4+
functions declarations.
5+
6+
That is, `#[ffi_const]` functions shall have no effects except for its return
7+
value, which can only depend on the values of the function parameters, and is
8+
not affected by changes to the observable state of the program.
9+
10+
Applying the `#[ffi_const]` attribute to a function that violates these
11+
requirements is undefined behaviour.
12+
13+
This attribute enables Rust to perform common optimizations, like sub-expression
14+
elimination, and it can avoid emitting some calls in repeated invocations of the
15+
function with the same argument values regardless of other operations being
16+
performed in between these functions calls (as opposed to `#[ffi_pure]`
17+
functions).
18+
19+
## Pitfalls
20+
21+
A `#[ffi_const]` function can only read global memory that would not affect
22+
its return value for the whole execution of the program (e.g. immutable global
23+
memory). `#[ffi_const]` functions are referentially-transparent and therefore
24+
more strict than `#[ffi_pure]` functions.
25+
26+
A common pitfall involves applying the `#[ffi_const]` attribute to a
27+
function that reads memory through pointer arguments which do not necessarily
28+
point to immutable global memory.
29+
30+
A `#[ffi_const]` function that returns unit has no effect on the abstract
31+
machine's state, and a `#[ffi_const]` function cannot be `#[ffi_pure]`.
32+
33+
A `#[ffi_const]` function must not diverge, neither via a side effect (e.g. a
34+
call to `abort`) nor by infinite loops.
35+
36+
When translating C headers to Rust FFI, it is worth verifying for which targets
37+
the `const` attribute is enabled in those headers, and using the appropriate
38+
`cfg` macros in the Rust side to match those definitions. While the semantics of
39+
`const` are implemented identically by many C and C++ compilers, e.g., clang,
40+
[GCC], [ARM C/C++ compiler], [IBM ILE C/C++], etc. they are not necessarily
41+
implemented in this way on all of them. It is therefore also worth verifying
42+
that the semantics of the C toolchain used to compile the binary being linked
43+
against are compatible with those of the `#[ffi_const]`.
44+
45+
[ARM C/C++ compiler]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/Cacgigch.html
46+
[GCC]: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-const-function-attribute
47+
[IBM ILE C/C++]: https://www.ibm.com/support/knowledgecenter/fr/ssw_ibm_i_71/rzarg/fn_attrib_const.htm
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# `ffi_pure`
2+
3+
The `#[ffi_pure]` attribute applies clang's `pure` attribute to foreign
4+
functions declarations.
5+
6+
That is, `#[ffi_pure]` functions shall have no effects except for its return
7+
value, which shall not change across two consecutive function calls with
8+
the same parameters.
9+
10+
Applying the `#[ffi_pure]` attribute to a function that violates these
11+
requirements is undefined behavior.
12+
13+
This attribute enables Rust to perform common optimizations, like sub-expression
14+
elimination and loop optimizations. Some common examples of pure functions are
15+
`strlen` or `memcmp`.
16+
17+
These optimizations are only applicable when the compiler can prove that no
18+
program state observable by the `#[ffi_pure]` function has changed between calls
19+
of the function, which could alter the result. See also the `#[ffi_const]`
20+
attribute, which provides stronger guarantees regarding the allowable behavior
21+
of a function, enabling further optimization.
22+
23+
## Pitfalls
24+
25+
A `#[ffi_pure]` function can read global memory through the function
26+
parameters (e.g. pointers), globals, etc. `#[ffi_pure]` functions are not
27+
referentially-transparent, and are therefore more relaxed than `#[ffi_const]`
28+
functions.
29+
30+
However, accesing global memory through volatile or atomic reads can violate the
31+
requirement that two consecutive function calls shall return the same value.
32+
33+
A `pure` function that returns unit has no effect on the abstract machine's
34+
state.
35+
36+
A `#[ffi_pure]` function must not diverge, neither via a side effect (e.g. a
37+
call to `abort`) nor by infinite loops.
38+
39+
When translating C headers to Rust FFI, it is worth verifying for which targets
40+
the `pure` attribute is enabled in those headers, and using the appropriate
41+
`cfg` macros in the Rust side to match those definitions. While the semantics of
42+
`pure` are implemented identically by many C and C++ compilers, e.g., clang,
43+
[GCC], [ARM C/C++ compiler], [IBM ILE C/C++], etc. they are not necessarily
44+
implemented in this way on all of them. It is therefore also worth verifying
45+
that the semantics of the C toolchain used to compile the binary being linked
46+
against are compatible with those of the `#[ffi_pure]`.
47+
48+
49+
[ARM C/C++ compiler]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/Cacigdac.html
50+
[GCC]: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-pure-function-attribute
51+
[IBM ILE C/C++]: https://www.ibm.com/support/knowledgecenter/fr/ssw_ibm_i_71/rzarg/fn_attrib_pure.htm

src/librustc_codegen_llvm/attributes.rs

+6
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,12 @@ pub fn from_fn_attrs(cx: &CodegenCx<'ll, 'tcx>, llfn: &'ll Value, instance: ty::
284284
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::FFI_RETURNS_TWICE) {
285285
Attribute::ReturnsTwice.apply_llfn(Function, llfn);
286286
}
287+
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::FFI_PURE) {
288+
Attribute::ReadOnly.apply_llfn(Function, llfn);
289+
}
290+
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::FFI_CONST) {
291+
Attribute::ReadNone.apply_llfn(Function, llfn);
292+
}
287293
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NAKED) {
288294
naked(llfn, true);
289295
}

src/librustc_error_codes/error_codes.rs

+3
Original file line numberDiff line numberDiff line change
@@ -616,4 +616,7 @@ E0754: include_str!("./error_codes/E0754.md"),
616616
E0724, // `#[ffi_returns_twice]` is only allowed in foreign functions
617617
E0726, // non-explicit (not `'_`) elided lifetime in unsupported position
618618
// E0738, // Removed; errored on `#[track_caller] fn`s in `extern "Rust" { ... }`.
619+
E0755, // `#[ffi_pure]` is only allowed on foreign functions
620+
E0756, // `#[ffi_const]` is only allowed on foreign functions
621+
E0757, // `#[ffi_const]` functions cannot be `#[ffi_pure]`
619622
}

src/librustc_feature/active.rs

+6
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,12 @@ declare_features! (
565565
/// Allow conditional compilation depending on rust version
566566
(active, cfg_version, "1.45.0", Some(64796), None),
567567

568+
/// Allows the use of `#[ffi_pure]` on foreign functions.
569+
(active, ffi_pure, "1.45.0", Some(58329), None),
570+
571+
/// Allows the use of `#[ffi_const]` on foreign functions.
572+
(active, ffi_const, "1.45.0", Some(58328), None),
573+
568574
// -------------------------------------------------------------------------
569575
// feature-group-end: actual feature gates
570576
// -------------------------------------------------------------------------

src/librustc_feature/builtin_attrs.rs

+2
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,8 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
331331
),
332332

333333
gated!(ffi_returns_twice, Whitelisted, template!(Word), experimental!(ffi_returns_twice)),
334+
gated!(ffi_pure, Whitelisted, template!(Word), experimental!(ffi_pure)),
335+
gated!(ffi_const, Whitelisted, template!(Word), experimental!(ffi_const)),
334336
gated!(track_caller, Whitelisted, template!(Word), experimental!(track_caller)),
335337
gated!(
336338
register_attr, CrateLevel, template!(List: "attr1, attr2, ..."),

src/librustc_middle/middle/codegen_fn_attrs.rs

+6
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ bitflags! {
7777
const NO_SANITIZE_THREAD = 1 << 14;
7878
/// All `#[no_sanitize(...)]` attributes.
7979
const NO_SANITIZE_ANY = Self::NO_SANITIZE_ADDRESS.bits | Self::NO_SANITIZE_MEMORY.bits | Self::NO_SANITIZE_THREAD.bits;
80+
/// #[ffi_pure]: applies clang's `pure` attribute to a foreign function
81+
/// declaration.
82+
const FFI_PURE = 1 << 15;
83+
/// #[ffi_const]: applies clang's `const` attribute to a foreign function
84+
/// declaration.
85+
const FFI_CONST = 1 << 16;
8086
}
8187
}
8288

src/librustc_span/symbol.rs

+2
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ symbols! {
323323
f32,
324324
f64,
325325
feature,
326+
ffi_const,
327+
ffi_pure,
326328
ffi_returns_twice,
327329
field,
328330
field_init_shorthand,

src/librustc_typeck/collect.rs

+37
Original file line numberDiff line numberDiff line change
@@ -2374,6 +2374,43 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
23742374
)
23752375
.emit();
23762376
}
2377+
} else if attr.check_name(sym::ffi_pure) {
2378+
if tcx.is_foreign_item(id) {
2379+
if attrs.iter().any(|a| a.check_name(sym::ffi_const)) {
2380+
// `#[ffi_const]` functions cannot be `#[ffi_pure]`
2381+
struct_span_err!(
2382+
tcx.sess,
2383+
attr.span,
2384+
E0757,
2385+
"`#[ffi_const]` function cannot be `#[ffi_pure]`"
2386+
)
2387+
.emit();
2388+
} else {
2389+
codegen_fn_attrs.flags |= CodegenFnAttrFlags::FFI_PURE;
2390+
}
2391+
} else {
2392+
// `#[ffi_pure]` is only allowed on foreign functions
2393+
struct_span_err!(
2394+
tcx.sess,
2395+
attr.span,
2396+
E0755,
2397+
"`#[ffi_pure]` may only be used on foreign functions"
2398+
)
2399+
.emit();
2400+
}
2401+
} else if attr.check_name(sym::ffi_const) {
2402+
if tcx.is_foreign_item(id) {
2403+
codegen_fn_attrs.flags |= CodegenFnAttrFlags::FFI_CONST;
2404+
} else {
2405+
// `#[ffi_const]` is only allowed on foreign functions
2406+
struct_span_err!(
2407+
tcx.sess,
2408+
attr.span,
2409+
E0756,
2410+
"`#[ffi_const]` may only be used on foreign functions"
2411+
)
2412+
.emit();
2413+
}
23772414
} else if attr.check_name(sym::rustc_allocator_nounwind) {
23782415
codegen_fn_attrs.flags |= CodegenFnAttrFlags::RUSTC_ALLOCATOR_NOUNWIND;
23792416
} else if attr.check_name(sym::naked) {

src/test/codegen/ffi-const.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// compile-flags: -C no-prepopulate-passes
2+
#![crate_type = "lib"]
3+
#![feature(ffi_const)]
4+
5+
pub fn bar() { unsafe { foo() } }
6+
7+
extern {
8+
// CHECK-LABEL: declare void @foo()
9+
// CHECK-SAME: [[ATTRS:#[0-9]+]]
10+
// CHECK-DAG: attributes [[ATTRS]] = { {{.*}}readnone{{.*}} }
11+
#[ffi_const] pub fn foo();
12+
}

src/test/codegen/ffi-pure.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// compile-flags: -C no-prepopulate-passes
2+
#![crate_type = "lib"]
3+
#![feature(ffi_pure)]
4+
5+
pub fn bar() { unsafe { foo() } }
6+
7+
extern {
8+
// CHECK-LABEL: declare void @foo()
9+
// CHECK-SAME: [[ATTRS:#[0-9]+]]
10+
// CHECK-DAG: attributes [[ATTRS]] = { {{.*}}readonly{{.*}} }
11+
#[ffi_pure] pub fn foo();
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#![crate_type = "lib"]
2+
3+
extern {
4+
#[ffi_const] //~ ERROR the `#[ffi_const]` attribute is an experimental feature
5+
pub fn foo();
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0658]: the `#[ffi_const]` attribute is an experimental feature
2+
--> $DIR/feature-gate-ffi_const.rs:4:5
3+
|
4+
LL | #[ffi_const]
5+
| ^^^^^^^^^^^^
6+
|
7+
= note: see issue #58328 <https://github.com/rust-lang/rust/issues/58328> for more information
8+
= help: add `#![feature(ffi_const)]` to the crate attributes to enable
9+
10+
error: aborting due to previous error
11+
12+
For more information about this error, try `rustc --explain E0658`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#![crate_type = "lib"]
2+
3+
extern {
4+
#[ffi_pure] //~ ERROR the `#[ffi_pure]` attribute is an experimental feature
5+
pub fn foo();
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0658]: the `#[ffi_pure]` attribute is an experimental feature
2+
--> $DIR/feature-gate-ffi_pure.rs:4:5
3+
|
4+
LL | #[ffi_pure]
5+
| ^^^^^^^^^^^
6+
|
7+
= note: see issue #58329 <https://github.com/rust-lang/rust/issues/58329> for more information
8+
= help: add `#![feature(ffi_pure)]` to the crate attributes to enable
9+
10+
error: aborting due to previous error
11+
12+
For more information about this error, try `rustc --explain E0658`.

src/test/ui/ffi_const.rs

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#![feature(ffi_const)]
2+
#![crate_type = "lib"]
3+
4+
#[ffi_const] //~ ERROR `#[ffi_const]` may only be used on foreign functions
5+
pub fn foo() {}

src/test/ui/ffi_const.stderr

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error[E0756]: `#[ffi_const]` may only be used on foreign functions
2+
--> $DIR/ffi_const.rs:4:1
3+
|
4+
LL | #[ffi_const]
5+
| ^^^^^^^^^^^^
6+
7+
error: aborting due to previous error
8+

src/test/ui/ffi_const2.rs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#![feature(ffi_const, ffi_pure)]
2+
3+
extern {
4+
#[ffi_pure] //~ ERROR `#[ffi_const]` function cannot be `#[ffi_pure]`
5+
#[ffi_const]
6+
pub fn baz();
7+
}
8+
9+
fn main() {
10+
unsafe { baz() };
11+
}

src/test/ui/ffi_const2.stderr

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error[E0757]: `#[ffi_const]` function cannot be `#[ffi_pure]`
2+
--> $DIR/ffi_const2.rs:4:5
3+
|
4+
LL | #[ffi_pure]
5+
| ^^^^^^^^^^^
6+
7+
error: aborting due to previous error
8+

src/test/ui/ffi_pure.rs

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#![feature(ffi_pure)]
2+
#![crate_type = "lib"]
3+
4+
#[ffi_pure] //~ ERROR `#[ffi_pure]` may only be used on foreign functions
5+
pub fn foo() {}

src/test/ui/ffi_pure.stderr

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error[E0755]: `#[ffi_pure]` may only be used on foreign functions
2+
--> $DIR/ffi_pure.rs:4:1
3+
|
4+
LL | #[ffi_pure]
5+
| ^^^^^^^^^^^
6+
7+
error: aborting due to previous error
8+

0 commit comments

Comments
 (0)