// Multiplexer utilities // Copyright (C) 2020-2024 Free Software Foundation, Inc. // // This file is part of GCC. // // GCC is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free // Software Foundation; either version 3, or (at your option) any later // version. // // GCC is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License // for more details. // // You should have received a copy of the GNU General Public License // along with GCC; see the file COPYING3. If not see // . #ifndef GCC_MUX_UTILS_H #define GCC_MUX_UTILS_H 1 // A class that stores a choice "A or B", where A has type T1 * and B has // type T2 *. Both T1 and T2 must have an alignment greater than 1, since // the low bit is used to identify B over A. T1 and T2 can be the same. // // A can be a null pointer but B cannot. // // Barring the requirement that B must be nonnull, using the class is // equivalent to using: // // union { T1 *A; T2 *B; }; // // and having a separate tag bit to indicate which alternative is active. // However, using this class can have two advantages over a union: // // - It avoids the need to find somewhere to store the tag bit. // // - The compiler is aware that B cannot be null, which can make checks // of the form: // // if (auto *B = mux.dyn_cast ()) // // more efficient. With a union-based representation, the dyn_cast // check could fail either because MUX is an A or because MUX is a // null B, both of which require a run-time test. With a pointer_mux, // only a check for MUX being A is needed. template class pointer_mux { public: // Return an A pointer with the given value. static pointer_mux first (T1 *); // Return a B pointer with the given (nonnull) value. static pointer_mux second (T2 *); pointer_mux () = default; // Create a null A pointer. pointer_mux (std::nullptr_t) : m_ptr (nullptr) {} // Create an A or B pointer with the given value. This is only valid // if T1 and T2 are distinct and if T can be resolved to exactly one // of them. template::value != std::is_convertible::value>::type> pointer_mux (T *ptr); // Return true unless the pointer is a null A pointer. explicit operator bool () const { return m_ptr; } // Assign A and B pointers respectively. void set_first (T1 *ptr) { *this = first (ptr); } void set_second (T2 *ptr) { *this = second (ptr); } // Return true if the pointer is an A pointer. bool is_first () const { return !(uintptr_t (m_ptr) & 1); } // Return true if the pointer is a B pointer. bool is_second () const { return uintptr_t (m_ptr) & 1; } // Return the contents of the pointer, given that it is known to be // an A pointer. T1 *known_first () const { return reinterpret_cast (m_ptr); } // Return the contents of the pointer, given that it is known to be // a B pointer. T2 *known_second () const { return reinterpret_cast (m_ptr - 1); } // If the pointer is an A pointer, return its contents, otherwise // return null. Thus a null return can mean that the pointer is // either a null A pointer or a B pointer. // // If all A pointers are nonnull, it is more efficient to use: // // if (ptr.is_first ()) // ...use ptr.known_first ()... // // over: // // if (T1 *a = ptr.first_or_null ()) // ...use a... T1 *first_or_null () const; // If the pointer is a B pointer, return its contents, otherwise // return null. Using: // // if (T1 *b = ptr.second_or_null ()) // ...use b... // // should be at least as efficient as: // // if (ptr.is_second ()) // ...use ptr.known_second ()... T2 *second_or_null () const; bool operator == (const pointer_mux &pm) const { return m_ptr == pm.m_ptr; } bool operator != (const pointer_mux &pm) const { return m_ptr != pm.m_ptr; } // Return true if the pointer is a T. // // This is only valid if T1 and T2 are distinct and if T can be // resolved to exactly one of them. The condition is checked using // a static assertion rather than SFINAE because it gives a clearer // error message. template bool is_a () const; // Assert that the pointer is a T and return it as such. See is_a // for the restrictions on T. template T as_a () const; // If the pointer is a T, return it as such, otherwise return null. // See is_a for the restrictions on T. template T dyn_cast () const; private: pointer_mux (char *ptr) : m_ptr (ptr) {} // Points to the first byte of an object for A pointers or the second // byte of an object for B pointers. Using a pointer rather than a // uintptr_t tells the compiler that second () can never return null, // and that second_or_null () is only null if is_first (). char *m_ptr; }; template inline pointer_mux pointer_mux::first (T1 *ptr) { gcc_checking_assert (!(uintptr_t (ptr) & 1)); return reinterpret_cast (ptr); } template inline pointer_mux pointer_mux::second (T2 *ptr) { gcc_checking_assert (ptr && !(uintptr_t (ptr) & 1)); return reinterpret_cast (ptr) + 1; } template template inline pointer_mux::pointer_mux (T *ptr) : m_ptr (reinterpret_cast (ptr)) { if (std::is_convertible::value) { gcc_checking_assert (m_ptr); m_ptr += 1; } } template inline T1 * pointer_mux::first_or_null () const { return is_first () ? known_first () : nullptr; } template inline T2 * pointer_mux::second_or_null () const { // Micro optimization that's effective as of GCC 11: compute the value // of the second pointer as an integer and test that, so that the integer // result can be reused as the pointer and so that all computation can // happen before a branch on null. This reduces the number of branches // needed for loops. return (uintptr_t (m_ptr) - 1) & 1 ? nullptr : known_second (); } template template inline bool pointer_mux::is_a () const { static_assert (std::is_convertible::value != std::is_convertible::value, "Ambiguous pointer type"); if (std::is_convertible::value) return is_second (); else return is_first (); } template template inline T pointer_mux::as_a () const { static_assert (std::is_convertible::value != std::is_convertible::value, "Ambiguous pointer type"); if (std::is_convertible::value) { gcc_checking_assert (is_second ()); return reinterpret_cast (m_ptr - 1); } else { gcc_checking_assert (is_first ()); return reinterpret_cast (m_ptr); } } template template inline T pointer_mux::dyn_cast () const { static_assert (std::is_convertible::value != std::is_convertible::value, "Ambiguous pointer type"); if (std::is_convertible::value) { if (is_second ()) return reinterpret_cast (m_ptr - 1); } else { if (is_first ()) return reinterpret_cast (m_ptr); } return nullptr; } #endif