Avoid copying MCInstrDesc instances because a future patch will change
them to find their implicit operands and operand info array based on
their own "this" pointer, so it will only work for MCInstrDescs in the
TargetInsts table, not for a copy of an MCInstrDesc at a different
address.
Details
- Reviewers
zuban32 mpaszkowski arsenm - Commits
- rG245e3dd94851: [MC] Do not copy MCInstrDescs. NFC.
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
llvm/include/llvm/MC/MCInstrDesc.h | ||
---|---|---|
200 | @dblaikie or any other C++ experts - does this seem reasonable? I was not sure if I should be following some kind of rule-of-3/5/7/11/13/... here. All I want to do is prevent users from accidentally writing MCInstrDesc MCID = ... instead of const MCInstrDesc &MCID = .... |
llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVMCCodeEmitter.cpp | ||
---|---|---|
64 | Doesn't this trigger compilation error (missing const)? |
llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVMCCodeEmitter.cpp | ||
---|---|---|
64 | Yes! Apparently you have to enable this target via LLVM_EXPERIMENTAL_TARGETS_TO_BUILD and I did not test that. Thanks for spotting it. |
llvm/include/llvm/MC/MCInstrDesc.h | ||
---|---|---|
200 | Generally I'd suggest that APIs shouldn't make awkward ergonomic choices to reduce the chance of accidents - there's lots of things that can be expensive to copy in C++ that still have the usual copy ctor/assignment operator. So if the thing is copyable - forcing users to do that copy in some uncommon way because the usual way is too error prone is, imho, not the right way to go - though I realize trying to teach people that this thing is expensive to copy isn't easy either. Hopefully updating a bunch of places that were copying will help set the right expectations. If it's really non-copyable in any way, then, yes, the rule of 5 would apply. Implement the copy ctor, copy assignment, move ctor, move assignment, and destructor. Sounds like deleting all the copy/move operations is what you want? Leave the dtor defaulted, maybe default the default (no-args) ctor? since that seems to be used in a test? (honestly, how are these objects constructed otherwise? I guess with aggregate init?) |
llvm/include/llvm/MC/MCInstrDesc.h | ||
---|---|---|
200 | Yes they are created with aggregate init, in a big static table generated by tablegen. They are not expensive to copy. They "can't" be copied in that some methods would no longer work correctly, because they rely on being able to access other data in the same table at a known offset from "this". |
llvm/include/llvm/MC/MCInstrDesc.h | ||
---|---|---|
200 | Ah, OK - yeah, totally disabling copy/move ops seems fine, then. So probably deleting all copy/move ctors, defaulting the dtor (I think that might still be implicit, despite the deleted copy/move ops, so you can leave that if it works without explicitly defaulting it). |
llvm/include/llvm/MC/MCInstrDesc.h | ||
---|---|---|
200 | Done, thanks! |
TL;DR: this doesn't work in C++20, maybe revert?
I'm surprised the aggregate init works given the deleted copy ctor here, but if it works, it works I guess *shrug*
The aggregate init works up to C++17, but not in C++20.
(The rules change from "no user-provided, inherited, or explicit constructors" to "no user-declared or inherited constructors").
I think the current status of building LLVM in C++20 mode is "theoretically supported, but no stable buildbot yet". It's being worked on.
Since C++20 compatibility is something we'll need to do in the medium-term, and incompatible constructs make building a good bot harder in the short-term, I'd reluctantly suggest reverting this.
(Eliminating the aggregate init or finding an alternative technique to break copy/move also seem fine, starting with a revert would still be nice if it's not trivial)
Interesting. Is there any easy way I can test building in C++20 mode on my own machine?
Looks like you should set -DCMAKE_CXX_STANDARD=20 (this lets me reproduce this class of error locally, with clang++-12 as host compiler).
cmake -DCMAKE_CXX_STANDARD=20 should give the C++20 build. However, make sure to have a ~new compiler. Clang 15 or GCC 12 should do the trick.
I have sent https://github.com/llvm/llvm-project/commit/f3d64644d8d75f9317421fe5b9b0f2d030a1a63f to unbreak our integrate, but actually want to help re-enable the enforcement that the type is not copied today.
To describe the problem, the following code breaks in C++20:
struct WantAggregate { WantAggregate(const WantAggregate&) = delete; int a; int b; }; WantAggregate a = {1,2}; // does not compile in C++20.
One way to workaround this would be to force an implicitly-deleted constructor:
struct NoCopy { NoCopy() = default; NoCopy(const &) = delete; }; struct WantAggregate { int a; int b; NoCopy delete_copy = {}; }; WantAggregate a = {1,2}; // works!
However, this approach increases the size of MCInstrDesc (the extra member needs to have an address). One way to workaround it is
[[no_unique_address]] NoCopy delete_copy = {}; // no increase in size, implicitly deleted copy!
Unfortunately, this attribute was added only in C++20. I am trying to figure out a way to make this work in both C++17 and C++20 without the size increase, but still struggling to find a way without a preprocessor.
(I'm assuming the size increase is undesirable, lmk if it's actually not a big deal. Using a member is probably the easiest workaround.)
@dblaikie or any other C++ experts - does this seem reasonable? I was not sure if I should be following some kind of rule-of-3/5/7/11/13/... here. All I want to do is prevent users from accidentally writing MCInstrDesc MCID = ... instead of const MCInstrDesc &MCID = ....