Skip to content

[ADT] Fix specialization of ValueIsPresent for PointerUnion #121847

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

Merged
merged 8 commits into from
Jan 10, 2025
Merged
8 changes: 4 additions & 4 deletions llvm/include/llvm/Support/Casting.h
Original file line number Diff line number Diff line change
Expand Up @@ -614,12 +614,12 @@ template <typename T> struct ValueIsPresent<std::optional<T>> {
static inline decltype(auto) unwrapValue(std::optional<T> &t) { return *t; }
};

// If something is "nullable" then we just compare it to nullptr to see if it
// exists.
// If something is "nullable" then we just cast it to bool to see if it exists.
template <typename T>
struct ValueIsPresent<T, std::enable_if_t<IsNullable<T>>> {
struct ValueIsPresent<
T, std::enable_if_t<IsNullable<T> && std::is_constructible_v<bool, T>>> {
using UnwrappedType = T;
static inline bool isPresent(const T &t) { return t != T(nullptr); }
static inline bool isPresent(const T &t) { return static_cast<bool>(t); }
Copy link
Contributor

Choose a reason for hiding this comment

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

If we're going down this route, it would probably make sense to also change the enable_if to check std::is_convertible<T, bool> instead?

I think you could also drop the separate std::optional specialization because it also has operator bool.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we're going down this route, it would probably make sense to also change the enable_if to check std::is_convertible<T, bool> instead?

I think it will make much more sense, will try.

I think you could also drop the separate std::optional specialization because it also has operator bool.

They are not quite the same, the specialization for std::optional derefernces the argument of unwrapValue().
This can probably be guarded by constexpr if, but personally I find the separate specialization clearer.

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 appears is_convertible only handles implicit conversions, i.e. it returns false for types with explicit operator bool(). I used is_constructible with swapped arguments instead.

static inline decltype(auto) unwrapValue(T &t) { return t; }
};

Expand Down
4 changes: 2 additions & 2 deletions llvm/lib/CodeGen/RegisterBankInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,10 @@ const TargetRegisterClass *RegisterBankInfo::constrainGenericRegister(

// If the register already has a class, fallback to MRI::constrainRegClass.
auto &RegClassOrBank = MRI.getRegClassOrRegBank(Reg);
if (isa<const TargetRegisterClass *>(RegClassOrBank))
if (isa_and_present<const TargetRegisterClass *>(RegClassOrBank))
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think either of these 2 instances should ever encounter a register without a set class or bank, this is papering over a different bug?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Input gMIR to instruction selector shouldn't contain registers without class/bank.
Such registers are created during instruction selection if an imported SelectionDAG pattern contains several instructions in the "destination DAG" of the pattern:

def : GCNPat <
  (UniformUnaryFrag<fabs> (v2f16 SReg_32:$src)),
  (S_AND_B32 SReg_32:$src, (S_MOV_B32 (i32 0x7fff7fff)))
>;

This is what -gen-global-isel generates for this pattern:

        GIR_MakeTempReg, /*TempRegID*/0, /*TypeID*/GILLT_s1,
        GIR_BuildMI, /*InsnID*/1, /*Opcode*/GIMT_Encode2(AMDGPU::S_MOV_B32),
        GIR_AddTempRegister, /*InsnID*/1, /*TempRegID*/0, /*TempRegFlags*/GIMT_Encode2(RegState::Define),
        ...
        GIR_ConstrainSelectedInstOperands, /*InsnID*/1,
        GIR_BuildRootMI, /*Opcode*/GIMT_Encode2(AMDGPU::S_AND_B32),
        ...
        GIR_AddSimpleTempRegister, /*InsnID*/0, /*TempRegID*/0,
        GIR_RootConstrainSelectedInstOperands,

GIR_MakeTempReg creates a register without class/bank for the result of the S_MOV_B32. The register gets its class when executing GIR_ConstrainSelectedInstOperands action, which calls this function, which calls MRI.setRegClass() at the end.

I don't know if this should be considered a bug. If it should, I can try to address it separately (probably in #121270).


(Unrelated to this PR). Note that the type of the temporary register is s1. It is chosen arbitrarily.

Copy link
Contributor

Choose a reason for hiding this comment

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

GIR_MakeTempReg creates a register without class/bank for the result of the S_MOV_B32.

As I mentioned in the other PR this is broken. In no context should an incomplete virtual register be used by an instruction

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 appears there is another context when class/bank may not be set: 7e1f66d

Copy link
Contributor

Choose a reason for hiding this comment

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

After regbankselect / in the selection pass, there must be a class or bank set. The null/null case is only valid before that, when a generic vreg must have a type

return MRI.constrainRegClass(Reg, &RC);

const RegisterBank *RB = cast<const RegisterBank *>(RegClassOrBank);
const auto *RB = dyn_cast_if_present<const RegisterBank *>(RegClassOrBank);
// Otherwise, all we can do is ensure the bank covers the class, and set it.
if (RB && !RB->covers(RC))
return nullptr;
Expand Down
4 changes: 2 additions & 2 deletions llvm/lib/Target/AMDGPU/SIRegisterInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3708,10 +3708,10 @@ const TargetRegisterClass *
SIRegisterInfo::getConstrainedRegClassForOperand(const MachineOperand &MO,
const MachineRegisterInfo &MRI) const {
const RegClassOrRegBank &RCOrRB = MRI.getRegClassOrRegBank(MO.getReg());
if (const RegisterBank *RB = dyn_cast<const RegisterBank *>(RCOrRB))
if (const auto *RB = dyn_cast_if_present<const RegisterBank *>(RCOrRB))
return getRegClassForTypeOnBank(MRI.getType(MO.getReg()), *RB);

if (const auto *RC = dyn_cast<const TargetRegisterClass *>(RCOrRB))
if (const auto *RC = dyn_cast_if_present<const TargetRegisterClass *>(RCOrRB))
return getAllocatableClass(RC);

return nullptr;
Expand Down
5 changes: 5 additions & 0 deletions llvm/unittests/ADT/PointerUnionTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@ TEST_F(PointerUnionTest, NewCastInfra) {
EXPECT_FALSE(isa<float *>(d4null));
EXPECT_FALSE(isa<long long *>(d4null));

EXPECT_FALSE(isa_and_present<int *>(i4null));
EXPECT_FALSE(isa_and_present<float *>(f4null));
EXPECT_FALSE(isa_and_present<long long *>(l4null));
EXPECT_FALSE(isa_and_present<double *>(d4null));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Previously, only the first isa_and_present returned false.


// test cast<>
EXPECT_EQ(cast<float *>(a), &f);
EXPECT_EQ(cast<int *>(b), &i);
Expand Down
Loading