This is an archive of the discontinued LLVM Phabricator instance.

[NFC] Use Optional<ProfileCount> to model invalid counts
ClosedPublic

Authored by mtrofin on Nov 13 2021, 10:07 PM.

Details

Summary

ProfileCount could model invalid values, but a user had no indication
that the getCount method could return bogus data. Optional<ProfileCount>
addresses that, because the user must dereference the optional. In
addition, the patch removes concept duplication.

Diff Detail

Event Timeline

mtrofin created this revision.Nov 13 2021, 10:07 PM
mtrofin requested review of this revision.Nov 13 2021, 10:07 PM
Herald added a project: Restricted Project. · View Herald TranscriptNov 13 2021, 10:07 PM

Generally like this sort of refactoring - though the const members are probably a fairly strong objection from me. The option to go further and make this a struct with optional helper member or non-member functions is less strenuous.

llvm/include/llvm/IR/Function.h
257–259

LLVM code doesn't generally use top-level const. Especially for members this makes things difficult - for instance you can't copy-assign objects with const members (so ProfileCount a, b; a = b; would be invalid) which usually isn't intended & can make things harder to work with.

261–266

Could this just be a struct at this point, maybe with an "isSynthetic" free function if testing "type == Synthetic" is too verbose?

The members are all both directly accessible, and can be initialized to any value, so the encapsulation doesn't seem to be providing anything?

llvm/lib/IR/Function.cpp
1927

separate patch could remove this else-after-return (& maybe add {} to the top-level if - it's a bit long to have no braces, even if it is only a single statement - or alternativeyl, invert that if statement and return early (if (!MD || !MD->getOperand(0)) return None; - oh, also could skip the getOperand checking and use dyn_cast_or_null on the next line instead))

mtrofin marked 2 inline comments as done.Nov 13 2021, 11:11 PM
mtrofin added inline comments.
llvm/include/llvm/IR/Function.h
257–259

Ack, I can do away with the const here, absence of setters achieves the same thing.

261–266

The value of encapsulation here is that it clarifies when the members are set: at construction only. Makes it so much easier (to me at least) to read the code by not forcing one to chase all references to a field and see whether it is set or read.

mtrofin updated this revision to Diff 387093.Nov 14 2021, 8:02 AM
mtrofin marked 2 inline comments as done.

Removed const from the fields of ProfileCount

dblaikie added inline comments.Nov 14 2021, 8:57 AM
llvm/include/llvm/IR/Function.h
261–266

any piece of code could still change the values with: someCount = ProfileCount(a, b) - and it doesn't look like these values are used in particularly complicated ways, most have a scope of maybe 5 lines (which is great - and cases where they have long/complicated scopes more at risk of spooky-action-at-a-distance - refactoring to reduce the length of functions/scopes, pull out pieces into separate functions would be good)

This isn't done for most types in C++ because it runs up against C++ design idioms. Some codebases might use top-level const on local variables more often to enforce this & it's certainly where I'd go if I were suggesting addressing this issue - but it does add a lot of extra text/verbosity, and also isn't really done in the LLVM codebase (It's done in a smattering of places, but I think the inconsistency is also unhelpful - there's loads of values that could be changed so reducing scopes in general probably has more payoff across all those values)

mtrofin added inline comments.Nov 14 2021, 12:03 PM
llvm/include/llvm/IR/Function.h
261–266

But profileCount.Count = 5 means nothing - i.e. the point of the type is to encapsulate something coming from metadata. so what would be the usecase for freely setting the flags? Meaning, what is lost with the current design?

I get the not const-ing the fields for scenarios like re-assigning a local var. They aren't const anymore.

mtrofin added inline comments.Nov 14 2021, 12:09 PM
llvm/include/llvm/IR/Function.h
261–266

But profileCount.Count = 5 means nothing - i.e. the point of the type is to encapsulate something coming from metadata. so what would be the usecase for freely setting the flags? Meaning, what is lost with the current design?

I get the not const-ing the fields for scenarios like re-assigning a local var. They aren't const anymore.

Oh, maybe there's a misunderstanding: my argument was about not going to a naked struct, not about const fields.

dblaikie accepted this revision.Nov 14 2021, 12:25 PM

Happy to approve the incremental improvement, even if it isn't as far as I'd go - up to you on the struct/class discussion.

llvm/include/llvm/IR/Function.h
261–266

(I think we're on the same page - discussing whether this could/should be a naked struct with two members, rather than having accessor member functions and private (albeit non-const) member variables)

Perhaps a reasonable summary of my perspective is: Since the values can be changed (by writing someCount = ProfileCount(a, b)) the read-only accessors don't provide the safety that a reader can assume the members are not modified - and we don't have that safety around most values/variables in C++ code, so I'm not sure why it's valuable to have it for this one type when all the other locals are modifiable (& this one is too, albeit slightly awkwardly by using assignment to the whole value rather than per-member).

The gain, to my mind, is a simpler API - like a function returning an int, we wouldn't usually wrap that in some other type that only provides read-access to it. This isn't much different.

It doesn't seem to me it provides the reader benefit you mentioned/motivated this design - that the reader doesn't have to worry about other mutations (because there can be mutations, by using the assignment) - and the scopes this exists in are quite small, so that reader benefit seems small and it's only one type/variable among many other mutable ones so I'm not sure it provides much value compared to reasoning about the rest of the variables/code in general.

This revision is now accepted and ready to land.Nov 14 2021, 12:25 PM
mtrofin added inline comments.Nov 14 2021, 7:03 PM
llvm/include/llvm/IR/Function.h
261–266

I think there is value in design that communicates intent; it's less about hard guarantees (even const could be dropped with a cast). It's more about leaving indication that "this is a value, it's meant to be a pair, not really meant for free modification".

I think the tension is between simpler API vs usable (as in maintainable). I think leaving hints in the way the API is designed, while one may argue it complicates it, communicates intent better and makes use-as-intended more likely; so less bugs, etc.

For example, the previous ProfileCount design. One could argue it was simpler - you could just getCount(), no need to dereference an Optional - but that simplicity actually made the invalid state basically undiscoverable. (i.e. you had to read implementation details to get to that)

This revision was automatically updated to reflect the committed changes.