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

Improve the documentation for std::hint::black_box. #62891

Merged
merged 1 commit into from
Aug 26, 2019

Conversation

vext01
Copy link
Contributor

@vext01 vext01 commented Jul 23, 2019

The other day a colleague was reviewing some of my code which was using black_box to block constant propogation. There was a little confusion because the documentation kind of implies that black_box is only useful for dead code elimination, and only in benchmarking scenarios.

The docs currently say:

A function that is opaque to the optimizer, to allow benchmarks to pretend to use outputs to assist in avoiding dead-code elimination.

Here is our discussion, in which I show (using godbolt) that a black box can also block constant propagation:
ykjit/yk#21 (comment)

This change makes the docstring for black_box a little more general, and while we are here, I've added an example (the same one from our discussion).

image

OK to go in?

@rust-highfive
Copy link
Collaborator

r? @cramertj

(rust_highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jul 23, 2019
@cramertj
Copy link
Member

IIRC on the RFC we were specifically discussing not making these kinds of promises for this function. cc @RalfJung

@RalfJung
Copy link
Member

Indeed, see rust-lang/rfcs#2360 for the long and detailed discussion and rust-lang/rfcs#2360 (comment) for a kind of summary with links to some key posts.

If anything the documentation should be changed to be more like the text of that RFC, and less about being "opaque to the optimizer".

@vext01
Copy link
Contributor Author

vext01 commented Jul 24, 2019

So something like:

an identity function that hints the compiler to be maximally pessimistic in terms of the assumptions about what black_box could do.

?

I guess I'm OK with it, even if it's a little vague.

@RalfJung
Copy link
Member

Yes, something like that. Maybe better with an explicit statement that this is done on a "best effort" basis. Quoting from the RFC:

However, Rust implementations are encouraged to assume that
black_box can use x in any possible valid way that Rust code is allowed to
without introducing undefined behavior in the calling code. That is,
implementations are encouraged to be maximally pessimistic in terms of
optimizations.

This property makes black_box useful for writing code in which certain
optimizations are not desired, but too unreliable when disabling these
optimizations is required for correctness.

@vext01
Copy link
Contributor Author

vext01 commented Jul 24, 2019

OK, so here's a preliminary proposal:

An identity function that hints the compiler to be maximally pessimistic about what black_box could do.

The compiler assumes that the argument to the black box could be read and/or mutated in a safe fashion (without undefined behaviour). In reality, the argument is neither read nor mutated.

This function is useful for (e.g.) preventing benchmarking code from being optimised away.

Questions:

  • Does black_box make any assumptions about side-effecting state other than its argument? IIRC, an inline assembler block is assumed to mutate anything, but I'm not sure if you are planning on exposing the implementation. From the RFC it sounds like the implementation may change.

  • If I understand correctly, a black box can also be used to anchor code relative to other black_box calls (preventing re-ordering). For example, if you sandwich code between two black boxes, then the code inside cannot be moved outside. Is that right? If so, should we mention this?

@hanna-kruppe
Copy link
Contributor

For example, if you sandwich code between two black boxes, then the code inside cannot be moved outside. Is that right? If so, should we mention this?

We don't want to/know how to properly specify that any more than for other restrictions on optimizations, so it certainly wouldn't be guaranteed. Plus, it's not even true of the current implementation. As the simplest possible counter-example, in this snippet:

black_box(...); // doesn't mention x
let y = x + 1;
black_box(...);

even if the x + 1 can't be optimized in any other way, it is certainly possible for LLVM to move it (or duplicate it) before or after the two black_box calls. The black_box calls are considered to have side effects by LLVM, but the addition doesn't have side effects, and so there is no reason why it should have to remain in the same position relative to side effecting instructions.

(If you mean "sandwiched" in terms of data-flow, i.e. in the above example if the first black_box produces x and the second uses y, then practically speaking it's probably not possible to elide the addition in that place. But it's certainly still possible to e.g. duplicate the x + 1 and compute it again afte rthe second black_box, e.g. if there's another use of y.)

@vext01
Copy link
Contributor Author

vext01 commented Jul 24, 2019

there is no reason why it should have to remain in the same position

OK, that wasn't my understanding. I thought that an inline asm block must be assumed to possibly modify x, or any variable for that matter, and thus it's position must not change.

Let's just not mention it?

@RalfJung
Copy link
Member

RalfJung commented Jul 24, 2019

I feel like you removed all the words that indicated that this is an entirely best-effort hint, and no guarantee. I propose something like:

An identity function that hints the compiler to be maximally pessimistic about what black_box could do.

The compiler is encouraged to assume that black_box can use x in any possible valid way that Rust code is allowed to without introducing undefined behavior in the calling code. This property makes black_box useful for writing code in which certain optimizations are not desired (such as preventing benchmarking code from being optimized away), but too unreliable when disabling these optimizations is required for correctness.

@vext01
Copy link
Contributor Author

vext01 commented Jul 24, 2019

Reads OK to me, but ultimately I think the Rust devs should decide.

(In light of our conversation, I'm not certain I fully understand the intention of black_box)

Copy link
Contributor

@gnzlbg gnzlbg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be better to just say that black_box is "A hint that might be treated as an unknown function by the optimizer."

And just leave it at that.

/// A function that is opaque to the optimizer, to allow benchmarks to
/// pretend to use outputs to assist in avoiding dead-code
/// elimination.
/// An identity function whose argument is opaque to the optimizer. This is useful for (e.g.)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is incorrect, the argument isn't opaque to the optimizer, and optimizations on the argument are possible, e.g., black_box(2 * 5) is currently optimized to black_box(10).

Copy link
Contributor

@gnzlbg gnzlbg Jul 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, "is opaque" is also incorrect - this function is a hint, there are no guarantees that it will do anything at all, and in some platforms, it does more than in others (e.g. using volatile reads vs inline assembly).

/// pretend to use outputs to assist in avoiding dead-code
/// elimination.
/// An identity function whose argument is opaque to the optimizer. This is useful for (e.g.)
/// benchmarking, where you want to avoid things like dead-code elimination and constant
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be part of the body of the documentation and not of the "brief" string.

I would leave this as "This is useful for, e.g., benchmarking, to prevent benchmarks from being optimized away". There are no guarantees whatsoever about which optimizations this intends to inhibit.

@gnzlbg
Copy link
Contributor

gnzlbg commented Jul 25, 2019

Reads OK to me, but ultimately I think the Rust devs should decide.

I think that what @RalfJung proposes here (#62891 (comment)) is the best we can do, and from the RFC there is consensus that these are the semantics that the language team wants.

The only open issue in the RFC there is the one raised by @cramertj about giving this intrinsic a clearer name like bench_black_box, but this PR is not the place to do that.

cc @Centril @eddyb thoughts ?

@Centril
Copy link
Contributor

Centril commented Jul 25, 2019

cc @Centril @eddyb thoughts ?

Other than what has been said already, I think we should point out various scenarios people would think to use black_box for and how they cannot be used for them. (i.e. we should provide examples of "correctness" purposes, e.g. making UB go away and for security applications).

I think we should really emphasize the bit about "identity function" being the only guarantee a bit more.

@gnzlbg
Copy link
Contributor

gnzlbg commented Jul 25, 2019

Other than what has been said already, I think we should point out various scenarios people would think to use black_box for and how they cannot be used for them. (i.e. we should provide examples of "correctness" purposes, e.g. making UB go away and for security applications).

I think we should do this as well, but not necessarily in this PR. I'd leave it up to @vext01 to decide if they feel like doing that. If they don't, we should definitely open an issue to track doing that.

I think we should really emphasize the bit about "identity function" being the only guarantee a bit more.

We could add a sentence after the last bit that says "but too unreliable when disabling these optimizations is required for correctness.", e.g., "The only guaranteed semantics of black_box is that it is the identity function and Rust is allowed to optimize it as such, for example, removing it completely replacing black_box(x) with just x.".

That should make it clear that replacing black_box(x) with x is an allowed optimization and that users cannot write code that relies on it not happening for correctness.

@Centril
Copy link
Contributor

Centril commented Jul 25, 2019

I think we should do this as well, but not necessarily in this PR.

Yeah entirely fair. At any point before stabilization should do from my POV. :)

@edmilsonefs
Copy link

Hey! This is a ping from triage, we would like to know if you @eddyb could give us a few minutes to share your thoughts on it.

@vext01 There is anything you would like to say/share based on the last responses you got so far?

Thanks.

@vext01
Copy link
Contributor Author

vext01 commented Jul 30, 2019

@vext01 There is anything you would like to say/share based on the last responses you got so far?

I don't mind making the change if we can come to a consensus.

I'm not going to suggest anything myself, as I'm not sure I fully understand the intention of black_box. I had thought of it in terms of its implementation (opaque inline asm), but I think the Rust devs may have something else in mind... I got lost a while back I'm afraid.

@hdhoang
Copy link
Contributor

hdhoang commented Aug 9, 2019

Second reviewer ping from triage, pinging reviewer from T-compiler @varkor

@varkor
Copy link
Member

varkor commented Aug 11, 2019

I agree that @RalfJung's suggestion is the most accurate description, possibly with @gnzlbg's addition. I'd be happy with an updated comment along those lines.

@totsteps
Copy link
Member

Ping from triage, @vext01 as per varkor's comment can you please update the description as such.

Thanks

@vext01
Copy link
Contributor Author

vext01 commented Aug 21, 2019

I'm happy to make the edit with one clarification: I don't understand the last part of @RalfJung's suggestion.

If we elide the bracketed section for a moment, it reads:

This property makes black_box useful for writing code in which certain optimizations are not desired, but too unreliable when disabling these optimizations is required for correctness.

Seems to need a re-wording?

@Centril
Copy link
Contributor

Centril commented Aug 21, 2019

This property makes black_box useful for writing code in which certain optimizations are not desired, but too unreliable when disabling these optimizations is required for correctness.

Seems to need a re-wording?

Something like:

This property makes black_box useful for writing code in which certain optimizations are not desired. However, this also makes black_box too unreliable when disabling these optimizations is required for correctness.

@vext01
Copy link
Contributor Author

vext01 commented Aug 21, 2019

However, this also makes black_box too unreliable when disabling these optimizations is required for correctness.

The revised comment makes sense, but if I understand correctly, you are saying "you can't rely on black_box to definitely do anything". If that's the case, I question why it exists it at all. (I don't see why an opaque asm block wouldn't always block Rust from seeing through it).

Regardless, if you are all happy with that wording I'll make the change.

@Centril
Copy link
Contributor

Centril commented Aug 21, 2019

The revised comment makes sense, but if I understand correctly, you are saying "you can't rely on black_box to definitely do anything". If that's the case, I question why it exists it at all. (I don't see why an opaque asm block wouldn't always block Rust from seeing through it).

You can definitely rely on black_box 1) moving the input, 2) returning the value back, 3) nothing else (it is only guaranteed to observably behave exactly like fn black_box<T>(x: T) -> T { x }.
(See the RFC for a discussion.)

@vext01
Copy link
Contributor Author

vext01 commented Aug 21, 2019

I've pushed the change, but honestly I'm not sure how much of an improvement it is. I think the wording will confuse the average Rust user.

If you folks think this makes sense, I can squash.

@gnzlbg
Copy link
Contributor

gnzlbg commented Aug 21, 2019

If that's the case, I question why it exists it at all.

Because there are many use cases, like writing benchmarks, for which this guarantee is not necessary.

(I don't see why an opaque asm block wouldn't always block Rust from seeing through it).

We can't use asm blocks on all supported targets because not all of them allow inline assembly, but even for the targets in which we do, nothing prevents LLVM from starting doing optimization on inline assembly blocks, and optimizing the block we are using to nothing would be trivial.

@vext01
Copy link
Contributor Author

vext01 commented Aug 21, 2019

Thanks @gnzlbg -- that was the missing info.

In light of this, let me propose a revision to the wording. Watch this space.

src/libcore/hint.rs Outdated Show resolved Hide resolved
src/libcore/hint.rs Outdated Show resolved Hide resolved
/// that Rust code is allowed to without introducing undefined behavior in the calling code. This
/// property makes `black_box` useful for writing code in which certain optimizations are not
/// desired. However, this also makes `black_box` too unreliable when disabling these optimizations
/// is required for correctness.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would probably be good to include an example using #[bench] specifically.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather punt that to some follow up PR. If anything I'd require opening a tracking issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I'm running out of steam for black_box :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure :)

Copy link
Member

@RalfJung RalfJung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Let's see what CI says.

@vext01
Copy link
Contributor Author

vext01 commented Aug 24, 2019

CI says yes. Shall I squash?

@RalfJung
Copy link
Member

RalfJung commented Aug 24, 2019 via email

@vext01
Copy link
Contributor Author

vext01 commented Aug 24, 2019

Splat.

@RalfJung
Copy link
Member

Thanks!

@gnzlbg @Centril are the last edits I suggested fine for you?

@Centril
Copy link
Contributor

Centril commented Aug 24, 2019

@RalfJung Good improvement; looks great. I think we can extend with examples of dos and don'ts in follow up PRs, but this is a good start.

@gnzlbg
Copy link
Contributor

gnzlbg commented Aug 24, 2019

LGTM

@RalfJung
Copy link
Member

@bors r=RalfJung,Centril,gnzlbg

@bors
Copy link
Contributor

bors commented Aug 25, 2019

📌 Commit a4b3dbe has been approved by RalfJung,Centril,gnzlbg

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 25, 2019
@bors
Copy link
Contributor

bors commented Aug 25, 2019

⌛ Testing commit a4b3dbe with merge b3145e9b935ace63f0e3adb416f14f60b8bf4db3...

@bors
Copy link
Contributor

bors commented Aug 25, 2019

💥 Test timed out

@bors bors added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Aug 25, 2019
@RalfJung
Copy link
Member

@bors retry

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 25, 2019
@bors
Copy link
Contributor

bors commented Aug 26, 2019

⌛ Testing commit a4b3dbe with merge e2b4165...

bors added a commit that referenced this pull request Aug 26, 2019
…ril,gnzlbg

Improve the documentation for std::hint::black_box.

The other day a colleague was reviewing some of my code which was using `black_box` to block constant propogation. There was a little confusion because the documentation kind of implies that `black_box` is only useful for dead code elimination, and only in benchmarking scenarios.

The docs currently say:

> A function that is opaque to the optimizer, to allow benchmarks to pretend to use outputs to assist in avoiding dead-code elimination.

Here is our discussion, in which I show (using godbolt) that a black box can also block constant propagation:
ykjit/yk#21 (comment)

This change makes the docstring for `black_box` a little more general, and while we are here, I've added an example (the same one from our discussion).

![image](https://user-images.githubusercontent.com/604955/61701322-ddf1e400-ad35-11e9-878c-b5b44a20770c.png)

OK to go in?
@bors
Copy link
Contributor

bors commented Aug 26, 2019

☀️ Test successful - checks-azure
Approved by: RalfJung,Centril,gnzlbg
Pushing e2b4165 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Aug 26, 2019
@bors bors merged commit a4b3dbe into rust-lang:master Aug 26, 2019
@vext01 vext01 deleted the improve-black-box-docs branch August 27, 2019 08:54
@vext01
Copy link
Contributor Author

vext01 commented Aug 27, 2019

Thanks all.

I've raised an issue for the rustdoc bug:
#63944

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Projects
None yet
Development

Successfully merging this pull request may close these issues.