Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracking issue for std::hint::black_box #64102

Closed
2 of 5 tasks
Centril opened this issue Sep 2, 2019 · 95 comments
Closed
2 of 5 tasks

Tracking issue for std::hint::black_box #64102

Centril opened this issue Sep 2, 2019 · 95 comments
Labels
B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. Libs-Tracked Libs issues that are tracked on the team's project board. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@Centril
Copy link
Contributor

Centril commented Sep 2, 2019

This is a tracking issue for the RFC std::hint:_black_box.

Original RFC: RFC 2360

Public API:

// std::hint

pub fn black_box<T>(dummy: T) -> T;

Steps:

  • Implementation
  • FCP
  • Stabilization PR

Unresolved questions:

  • const fn: it is unclear whether bench_black_box should be a const fn. If it
    were, that would hint that it cannot have any side-effects, or that it cannot
    do anything that const fns cannot do.

  • Naming: during the RFC discussion it was unclear whether black_box is the
    right name for this primitive but we settled on bench_black_box for the time
    being. We should resolve the naming before stabilization.

    Also, we might want to add other benchmarking hints in the future, like
    bench_input and bench_output, so we might want to put all of this
    into a bench sub-module within the core::hint module. That might
    be a good place to explain how the benchmarking hints should be used
    holistically.

    Some arguments in favor or against using "black box" are that:

    • pro: [black box] is a common term in computer programming, that conveys
      that nothing can be assumed about it except for its inputs and outputs.
      con: [black box] often hints that the function has no side-effects, but
      this is not something that can be assumed about this API.
    • con: _box has nothing to do with Box or box-syntax, which might be confusing

    Alternative names suggested: pessimize, unoptimize, unprocessed, unknown,
    do_not_optimize (Google Benchmark).

@Centril Centril added B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Sep 2, 2019
@Centril
Copy link
Contributor Author

Centril commented Sep 3, 2019

I believe the main implementation work remaining is to enhance the documentation of hint::black_box as well as to rename it.

@eddyb
Copy link
Member

eddyb commented Sep 3, 2019

An idea I wish I had earlier (but we can still try it out):

  1. use an intrinsic (required anyway for nice wasm/miri support)
  2. when codegening the intrinsic in a non---test crate:
    • don't generate the inline assembly (so LLVM can optimize right through it)
    • emit a warning that it should only be used in a test crate

Note that because this is a warning, and not an error, it doesn't add to the dreaded post-monomorphization errors. We could also just do 2. but not 1, if we need to.

#[inline] and/or generics could then be used to delay the codegen until the --test crate, so it can do its intended purpose.

Would be interesting to know what the benchmarking frameworks out there use, in terms of test runners, and if they end up in a rustc --test crate (i.e. via cargo test) or just a plain executable.

@clarfonthey
Copy link
Contributor

The term "fence" may also be appropriate, as it doesn't prevent optimisation of the value inside it, but rather, doesn't allow optimisations to cross the barrier.

For example, vec.push(bench_black_box(convoluted_way_to_compute_zero())) could still be optimised to vec.push(bench_black_box(0)), but vec.push couldn't use the knowledge that the value is zero to optimise itself.

@LukasKalbertodt
Copy link
Member

Another argument in favor of black_box: not only is it a common term in computer programming, but already used a lot in all kinds of documentation/resources about Rust. For example, benchmarking libraries such as Criterion use black_box and plenty of StackOverflow questions and answers use black_box. Also, I doubt that anyone would really think of Box when reading black_box, and even if, this confusion should be resolved quickly and I can't see how it would lead to bugs. I don't really see a good reason to change an established name for a questionable benefit.

@hanna-kruppe
Copy link
Contributor

I don't want to retreat ground that has been covered to exhaustion and beyond in the RFC discussion, but since all discussion here including the issue text is not even mentioning the key point that was blocking the black_box name it seems necessary to quote it again:

I'm strongly opposed to adding a function to the language called black_box that doesn't guarantee the expected semantics of a black_box function in other languages and systems. There are a number of important unsafe operations that rely on the ability to create a "black box" from the compiler to show that e.g. a pointer escapes or some other operation is not elided.

The semantics this concern was in reference to is (roughly) what's in the accepted RFC: this function is not actually guaranteed to be a "black box" to anyone, least of all the optimizer -- more explicitly: it is allowed to be implemented as completely transparent identity function, and behaves as such for purposes of e.g. whether a program is UB. Assuming nobody wants to re-litigate these semantics (doesn't seem like it so far, thank gods), anyone who wants the black_box name to be accepted has to convincingly argue that the aforementioned concern is actually a non-issue for some reason. You're invited to do that, but be aware that quibbling over e.g. the potential for confusion with Box isn't going to help with that.

By the way, the same concern probably applies to most names mentioned as alternatives so far, as they all stress the "blocks optimizations / blinds the compiler" angle without simultaneously highlighting that it's is only suitable for benchmarks, where surprisingly aggressive optimizations only lead to confusing results rather than UB or other serious wrongness.

@jonas-schievink jonas-schievink added C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. E-easy Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue. labels Oct 4, 2019
@gnzlbg
Copy link
Contributor

gnzlbg commented Dec 5, 2019

const fn: it is unclear whether bench_black_box should be a const fn. If it
were, that would hint that it cannot have any side-effects, or that it cannot
do anything that const fns cannot do.

We can implement this using the approach that kind of got consensus in #64683:

fn bench_black_box_rt<T>(x: T) -> T { asm!("....") }
const fn bench_black_box_ct<T>(x: T) -> T { x }
pub const fn bench_black_box<T>(x: T) { 
    const_eval_select(bench_black_box_ct, bench_black_box_rt)
}

cc @ralf @oli-obk

@ijackson
Copy link
Contributor

(Hello. I ended up reading this issue because I tried to solve the problem black_box is aimed at a different way, and found some to-me-surprising compiler behaviour - #74476. @the8472 helpfully referred me to black_box.)

Assuming nobody wants to re-litigate these semantics (doesn't seem like it so far, thank gods),

Sorry to pick up on this. I definitely do not want to re-litigate a decision which has already been made. However:

I skimread a lot of these discussions including https://github.com/rust-lang/rust-memory-model/issues/45 . Much of the compiler internals went over my head; and as a result I wasn't able to see why this had been decided this way. Of course I looked in the RFC which ought to mention this under Alternatives, but it doesn't seem to discuss it at all.

It seems to me that regardless of whether the semantics question is regarded as settled, it needs to be addressed in the RFC text. Especially if it was controversial! I.e. the Alternatives should have a section explaining why the proposal does not provide a useful semantic guarantee. The best description of the most desirable guarantee seems to me to be in rust-lang/rfcs#1484 (comment) or https://github.com/rust-lang/rust-memory-model/issues/45#issuecomment-374369870. Therefore it would probably be best for the RFC to quote one of those, or to paraphrase them, acknowledge that that would be the most useful thing to provide, and then explain why the RFC proposes something different.

Since the existing RFC was merged, maybe this would need to be done as part of an attempt at stabilisation.

With the current very weak semantics, I strongly agree with @hanna-kruppe's concerns over the misleading name.

@jonhoo
Copy link
Contributor

jonhoo commented Jul 27, 2020

I also agree with @hanna-kruppe that black_box is probably a misleading name for a function with the semantics in the RFC. To try and move discussion forward, here are some alternative suggestions that focus specifically on what the function does, rather than what it's "used for" or what it might do:

  • assume_used
  • mock_use
  • use_and
  • use_then
  • emulate_use
  • mark_used

I particularly like std::hint::assume_used. For the RFC example, this (I think) reads quite well (and accurate):

fn push_cap(v: &mut Vec<i32>) {
    for i in 0..4 {
        assume_used(v.as_ptr());
        v.push(bench_black_box(i));
        assume_used(v.as_ptr());
    }
}

It also works even when used as an identity function:

let x = assume_used(x);

Is still fairly clear doesn't change the value in any way.

@jonhoo
Copy link
Contributor

jonhoo commented Jul 28, 2020

I went ahead and created a PR that renames the method to assume_used in #74853 to encourage further directed discussion.

@tmiasko
Copy link
Contributor

tmiasko commented Jul 28, 2020

@jonhoo I wonder how would it go along with other assume-like functions in
the standard library. There is assume intrinsic and a family of assume_init
functions, for those it is responsibility of the caller to guarantee the thing
being assumed. In the proposed assume_used, the role of the callee.

@jonhoo
Copy link
Contributor

jonhoo commented Jul 28, 2020

@tmiasko I replied to your comment over in #74853 (comment). Figured it'd be good to keep all the discussion about this particular name suggestion to one PR.

jonhoo added a commit to jonhoo/rust that referenced this issue Jul 29, 2020
The current name is a legacy from before RFC 2360. The RFC calls the
method `bench_black_box`, though acknowledges that this name is not be
ideal either.

The main objection to the name during and since the RFC is that it is
not truly a black box to the compiler. Instead, the hint only encourages
the compiler to consider the passed-in value used. This in the hope that
the compiler will materialize that value somewhere, such as in memory or
in a register, and not eliminate the input as dead code.

This PR proposes to rename the method to `pretend_used`. This clearly
indicates the precise semantics the hint conveys to the compiler,
without suggesting that it disables further compiler optimizations. The
name also reads straightforwardly in code: `hint::pretend_used(x)` hints
to the compiler that it should pretend that `x` is used in some
arbitrary way.

`pretend_used` also rectifies the secondary naming concern the RFC
raised with the names `black_box` and `bench_black_box`; the potential
confusion with "boxed" values.

If this change lands, it completes the naming portion of rust-lang#64102.
@KodrAus KodrAus added the Libs-Tracked Libs issues that are tracked on the team's project board. label Jul 29, 2020
@eddyb
Copy link
Member

eddyb commented Aug 14, 2020

I was seeing some weird effects from using black_box in conjunction with counting instructions, and then I remembered that black_box's current incarnation has to place the value on the stack.

So I tried making my own, and ended up with this, which is sadly limited to values that fit in a register (godbolt):

pub fn better_black_box(mut x: u64) -> u64 {
    unsafe { asm!("/* {x} */", x = inout(reg) x); }
    x
}

From my brief testing, it seems to result in 3 less instructions than std::hint::black_box, at this time, as counted by the CPU (what perf stat calls instructions:u, although I'm using rdpmc directly to get my data).


Might be interesting to see if we could specialize the version in libstd (based on the type) so that it's cheaper, for at least integers, but maybe there's not really any point to that. cc @Amanieu

@Amanieu
Copy link
Member

Amanieu commented Aug 14, 2020

You don't even need specialization, you can just check size_of::<T> to see if it fits in a usize and then transmute_copy it. (godbolt)

@eddyb
Copy link
Member

eddyb commented Aug 14, 2020

You need to avoid the case when T may have destructors, as transmute_copy will unsafely duplicate the value, but other than that, seems like it could work great, thanks! (we could presumably also have cases for types smaller than usize, or types the size of two usizes, like &[T], as long as we don't introduce extra work beyond forcing the values into registers)

@m-ou-se
Copy link
Member

m-ou-se commented Aug 14, 2020

Adding options(nostack) removes the push and pop instructions as well:

            asm!("/* {x} */", x = inout(reg) int_x, options(nostack));
example::better_black_box:
        mov     rax, rdi
        #APP
        #NO_APP
        ret

@jonhoo
Copy link
Contributor

jonhoo commented Aug 25, 2020

My latest suggestion from #74853 was consume_and_produce. The idea being that it is a function that consumes the given value in some unspecified way, and produces a value in some unspecified way (that happens to be identical to the consumed value).

@pitaj
Copy link
Contributor

pitaj commented Oct 15, 2020

This hasn't seen some activity in a while. Has an acceptable default implementation for this been found? It looks like the latest "best implementation" would be a combination of the proposals by @eddyb, @Amanieu, and @m-ou-se

I made some modifications and created the following godbolt:

fn better_black_box<T>(mut x: T) -> T {
    unsafe {
        if mem::size_of::<T>() <= mem::size_of::<usize>() {
            let man_x = mem::ManuallyDrop::new(x);
            let mut int_x: usize = mem::transmute_copy(&man_x);
            asm!("/* {x} */", x = inout(reg) int_x, options(nostack));
            x = mem::transmute_copy(&int_x);
        } else {
            asm!("/* {x} */", x = in(reg) &mut x);
        }
    }
    x
}

It's a small modification from their code that makes it work for anything that'll fit in a usize (not sure if it's completely sound), and also fixes the destructor issue that @eddyb brought up, I think.

Has any consensus been come to on the name? If not, I'd like to humbly propose my own option: bench_identity. I think this name works well as it describes the actual behavior of the function (it behaves as an identity) and the purpose of the function (its use in benchmarking).

@Amanieu
Copy link
Member

Amanieu commented Oct 16, 2020

let mut int_x: usize = mem::transmute_copy(&man_x);

This line is unsound if x is smaller than usize, see the documentation for transmute_copy.

@rfcbot
Copy link

rfcbot commented Aug 23, 2022

Team member @m-ou-se has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Aug 23, 2022
@m-ou-se m-ou-se added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. I-lang-nominated Nominated for discussion during a lang team meeting. and removed I-libs-api-nominated Nominated for discussion during a libs-api team meeting. labels Aug 23, 2022
@m-ou-se
Copy link
Member

m-ou-se commented Aug 23, 2022

Seems like this should also involve T-lang? It is after all a language extension.

I've nominated this issue for lang team discussion.

@Lokathor
Copy link
Contributor

If this is already something that can be done with inline asm, as demonstrated above, then it's "just" a small helper function in the standard library based on an existing stable part of the language.

@RalfJung
Copy link
Member

Every inline asm block is a language extension, unless its effect on the Rust-visible state can be achieved without using inline asm.

Now in this case the effect on the Rust-visible change is a NOP, so it'd probably fine, but inline asm is sufficiently subtle and this a sufficiently high-profile feature that I feel it should be at least on their radar.

@rfcbot
Copy link

rfcbot commented Sep 6, 2022

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot added final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. and removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. labels Sep 6, 2022
@scottmcm
Copy link
Member

scottmcm commented Sep 6, 2022

We discussed this in the @rust-lang/lang meeting today, and agreed that from a lang perspective this is fine to exist, because a conforming implementation can have it do nothing at all. (Thus it being in std::hint.)


I would say it doing something useful is, like with inline, a quality-of-implementation issue, and up to compiler to decide how much effort is appropriate to spend on having it do what people want. For example, I would say that minimal-at-most effort should be spent on any "this optimization didn't get applied when I used black_box" issues. It might thus be appropriate to caveat its documentation even more than it already is.

@nikomatsakis nikomatsakis removed the I-lang-nominated Nominated for discussion during a lang team meeting. label Sep 13, 2022
@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. to-announce Announce this issue on triage meeting and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Sep 16, 2022
@rfcbot
Copy link

rfcbot commented Sep 16, 2022

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

This will be merged soon.

@apiraino apiraino removed the to-announce Announce this issue on triage meeting label Sep 22, 2022
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Sep 27, 2022
…=TaKO8Ki

Stabilize bench_black_box

This PR stabilize `feature(bench_black_box)`.

```rust
pub fn black_box<T>(dummy: T) -> T;
```

The FCP was completed in rust-lang#64102.

`@rustbot` label +T-libs-api -T-libs
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Sep 27, 2022
…=TaKO8Ki

Stabilize bench_black_box

This PR stabilize `feature(bench_black_box)`.

```rust
pub fn black_box<T>(dummy: T) -> T;
```

The FCP was completed in rust-lang#64102.

``@rustbot`` label +T-libs-api -T-libs
JohnTitor added a commit to JohnTitor/rust that referenced this issue Sep 28, 2022
…=TaKO8Ki

Stabilize bench_black_box

This PR stabilize `feature(bench_black_box)`.

```rust
pub fn black_box<T>(dummy: T) -> T;
```

The FCP was completed in rust-lang#64102.

`@rustbot` label +T-libs-api -T-libs
RalfJung pushed a commit to RalfJung/miri that referenced this issue Sep 29, 2022
Stabilize bench_black_box

This PR stabilize `feature(bench_black_box)`.

```rust
pub fn black_box<T>(dummy: T) -> T;
```

The FCP was completed in rust-lang/rust#64102.

`@rustbot` label +T-libs-api -T-libs
RalfJung pushed a commit to RalfJung/miri that referenced this issue Oct 4, 2022
Stabilize bench_black_box

This PR stabilize `feature(bench_black_box)`.

```rust
pub fn black_box<T>(dummy: T) -> T;
```

The FCP was completed in rust-lang/rust#64102.

`@rustbot` label +T-libs-api -T-libs
@Dylan-DPC
Copy link
Member

Closing this issue as it has been stabilised and wasn't tagged in the pr so it didn't get closed on merge

thomcc pushed a commit to tcdi/postgrestd that referenced this issue Feb 10, 2023
Stabilize bench_black_box

This PR stabilize `feature(bench_black_box)`.

```rust
pub fn black_box<T>(dummy: T) -> T;
```

The FCP was completed in rust-lang/rust#64102.

`@rustbot` label +T-libs-api -T-libs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. Libs-Tracked Libs issues that are tracked on the team's project board. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests