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

An error type for trace invalidation. #21

Merged
merged 2 commits into from
Jul 13, 2019
Merged

Conversation

vext01
Copy link
Contributor

@vext01 vext01 commented Jul 12, 2019

The meat of this PR is in the first commit (see the commit message for more) and then there's a boring style change.

tiri/src/lib.rs Outdated
#[ignore] // FIXME -- This will fail until we investigate why SIR is missing.
fn interp_simple_trace() {
let tracer = start_tracing(Some(TracingKind::SoftwareTracing));
let res = black_box(work(black_box(3), black_box(13)));
Copy link
Contributor

Choose a reason for hiding this comment

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

Surely there's no point in doing black_box(3) -- an integer can't realistically be optimised!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's to prevent constant propagation into the function body. Otherwise the body may be replaced by a value.

Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't the outer black_box prevent that happening?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not necessarily. The addition could still be removed. Ultimately it's the arguments to the addition operator that we must prevent the compiler from knowing ahead of time.

The outer black box is actually probably not necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(It's probably sufficient to only guard only one argument with a black box, too, but having two won't hurt)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As expected, the compiler performs constant propagation:
image

Copy link
Contributor

Choose a reason for hiding this comment

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

That's very interesting! black_box does not work as I expected, which is probably my fault.

OK, so going back to this PR ;) I now think we should drop the outer black_box call since it doesn't do anything useful in this context?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, I agree.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You've also highlighted that the Rust black_box docs could be improved. I might do a PR against upstream.

Copy link
Contributor

Choose a reason for hiding this comment

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

Cool!

pub enum InvalidTraceError {
/// There is no SIR for the DefId of a location in the trace.
NoSir(DefId),
/// Something went wrong in low-level compiler support.
Copy link
Contributor

Choose a reason for hiding this comment

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

This sentence doesn't parse.

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'm not sure what you mean. Seems OK here.

Does "Something went wrong in the compiler's tracing code" work better?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep!

@ltratt
Copy link
Contributor

ltratt commented Jul 12, 2019

One thing that might be worth thinking about is whether you want to Box your error. Basically if you don't expect errors to be returned very often, and if the size of the enum is bigger than usize (which it may well be already), then you'll reduce the size of the function's return type by Boxing errors. It's not easy to know when this is/isn't a good idea, unfortunately...

@vext01
Copy link
Contributor Author

vext01 commented Jul 12, 2019

A DefId is already bigger than a box, so it does make sense to use a box. We might aswell implement Error if we are going that route too. Then ? will work with functions returning Box<Error>.

@ltratt
Copy link
Contributor

ltratt commented Jul 12, 2019

We might aswell implement Error if we are going that route too.

Works for me.

@vext01
Copy link
Contributor Author

vext01 commented Jul 12, 2019

then you'll reduce the size of the function's return type by Boxing errors.

Ah interesting.

Remember what Jake showed us LLVM does? If a return value is larger than a register, it'll put it on the caller's stack and pass a reference into the callee. I'd have to check if we can make that happen from Rust, but that'd be more efficient than heap allocating.

Thoughts before I investigate?

@ltratt
Copy link
Contributor

ltratt commented Jul 12, 2019

Thoughts before I investigate?

I think it's worth investigating! I'd forgotten about that LLVM discovery.

@vext01
Copy link
Contributor Author

vext01 commented Jul 12, 2019

Successfully reproduced!

image

Notice how the return value is a formal parameter?

@ltratt
Copy link
Contributor

ltratt commented Jul 12, 2019

I'd like to pretend I can make that sense of that... but it's beyond me!

@vext01
Copy link
Contributor Author

vext01 commented Jul 12, 2019

The highlighted line of the right-hand side shows the LLVM IR for the call on line 15 of the Rust program on the left.

You can see that the signature of wiggle() has been transformed from:

pub fn wiggle() -> Result<(), BiggidyError>;

To:

pub fn wiggle(err: &Result<(), BiggidyError>);

So the storage for the return value is allocated on the stack before the call, and a reference is passed into the callee. The callee then populates the pre-allocated space with the (conceptual) return value. Very cool.

@ltratt
Copy link
Contributor

ltratt commented Jul 12, 2019

Aha, I see!

@vext01
Copy link
Contributor Author

vext01 commented Jul 12, 2019

So now the question is, do we prefer to leave the return value unboxed (thus using stack allocation) or box it and use heap allocation?

My gut says the former is more efficient because stack allocation amounts to only subtracting from the stack pointer, whereas heap allocation requires locating a spot large enough on the heap. Using the heap also contributes to memory fragmentation and I'd also guess has worse locality.

Would you agree?

@ltratt
Copy link
Contributor

ltratt commented Jul 12, 2019

Why don't you try on your example? My guess is that boxing it still transforms things into a parameter, at which point the boxing is largely pointless (unless the struct involved is really big -- at some point the waste of stack space must be a factor, but I doubt we're at that point).

@vext01
Copy link
Contributor Author

vext01 commented Jul 12, 2019

The opposite, because the Box fits in a register:

image

The call is:

%3 = tail call noalias align 8 dereferenceable_or_null(24) i64* @_ZN7example6wiggle17h8f5262ea6c257490E()

Notice the i64* return type? That's our Box, and there are no formal params.

@ltratt
Copy link
Contributor

ltratt commented Jul 12, 2019

That's probably because you've got () as the "success" part of the Result: put something in there which can't be squeezed in a pointer's spare bits, and I imagine it'll become bigger than a word.

@vext01
Copy link
Contributor Author

vext01 commented Jul 12, 2019

Yes, I'd expect the compiler to use the stack if the Ok case is larger than a register. Indeed:

call void @_ZN7example6wiggle17h4367e95236baf0f2E(%"core::result::Result<u128, alloc::boxed::Box<InvalidTraceError>>"* noalias nocapture nonnull sret dereferenceable(24) %_12)

@vext01
Copy link
Contributor Author

vext01 commented Jul 12, 2019

And what do we learn about Boxing the error type? I suppose we shouldn't as it seems LLVM does a good job of optimising it if it gets big. Agree?

@ltratt
Copy link
Contributor

ltratt commented Jul 12, 2019

Agreed.

@vext01
Copy link
Contributor Author

vext01 commented Jul 12, 2019

The last two small changes address the outstanding comments.

@ltratt
Copy link
Contributor

ltratt commented Jul 12, 2019

Please squash.

vext01 added 2 commits July 13, 2019 10:58
This makes crashes at least print some useful information instead of
just telling us that a None was unwrapped.

Also add a failing (but ignored for now) test which currently causes
TraceInvalid::NoSir. This means we have encountered a location for which
we have no SIR. A future PR will fix this, but first we will need some
debugging aids before we can understand exactly what's gone wrong.
@vext01 vext01 force-pushed the inavlid-trace-error-type branch from 7792849 to e192095 Compare July 13, 2019 09:58
@vext01
Copy link
Contributor Author

vext01 commented Jul 13, 2019

splat

@ltratt
Copy link
Contributor

ltratt commented Jul 13, 2019

bors r+

bors bot added a commit that referenced this pull request Jul 13, 2019
21: An error type for trace invalidation. r=ltratt a=vext01

The meat of this PR is in the first commit (see the commit message for more) and then there's a boring style change.

Co-authored-by: Edd Barrett <[email protected]>
@bors
Copy link
Contributor

bors bot commented Jul 13, 2019

Build succeeded

@bors bors bot merged commit e192095 into master Jul 13, 2019
@bors bors bot deleted the inavlid-trace-error-type branch July 13, 2019 11:14
bors added a commit to rust-lang/rust 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?
ltratt added a commit to ltratt/yk that referenced this pull request Apr 11, 2023
21: Start documenting our approach to PRs. r=ptersilie a=ltratt

Although our approach is heavily influenced by projects like Rust, it's sufficiently elaborate that newcomers find it difficult. This commit tries to capture both the process *and* the reasons for that process. It is necessarily
incomplete and imperfect: I'm sure we'll need to iterate on this for a little while before we feel fully happy with it. However, this is a start -- something is better than nothing!

Co-authored-by: Laurence Tratt <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants