Skip to content

Commit 6cc5d7e

Browse files
authored
Add an EnumMaskBase type (#6053)
This is a bit of an experiment to see if there's a reasonable way to write a shared enum type, rather than writing per-case wrappers for things like `HasTypeQualifiers` or the printing. I think it's a bit borderline complexity right now, but I'm not sure I can reduce it much further. This changes from things like `Internal::EnumClassName##RawEnum` to `Internal::EnumClassName##Data::RawEnum` so that the enum entries can have back references to bit shifts without needing to know the containing type name. Because I'm trying to reduce duplication between mask and non-mask enums, I did this to non-mask enums too. This was motivated by #6035 adding another enum mask (which will grow more entries, and is intended to switch if this is accepted), but I'm not using that PR as a base here because I didn't want the merge dependency.
1 parent 508a88e commit 6cc5d7e

17 files changed

+364
-225
lines changed

.clang-format

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,10 @@ InsertBraces: 'true'
1414
PointerAlignment: Left
1515
# We abuse control macros for formatting other kinds of macros.
1616
SpaceBeforeParens: ControlStatementsExceptControlMacros
17-
IfMacros: ['CARBON_DEFINE_RAW_ENUM_CLASS', 'CARBON_KIND_SWITCH']
17+
IfMacros:
18+
[
19+
'CARBON_DEFINE_RAW_ENUM_CLASS',
20+
'CARBON_DEFINE_RAW_ENUM_MASK',
21+
'CARBON_KIND_SWITCH',
22+
]
1823
StatementMacros: ['ABSTRACT']

common/BUILD

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,15 @@ cc_test(
159159
],
160160
)
161161

162+
cc_library(
163+
name = "enum_mask_base",
164+
hdrs = ["enum_mask_base.h"],
165+
deps = [
166+
":enum_base",
167+
"@llvm-project//llvm:Support",
168+
],
169+
)
170+
162171
cc_library(
163172
name = "error",
164173
hdrs = ["error.h"],

common/enum_base.h

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ class EnumBase : public Printable<DerivedT> {
165165
}
166166

167167
private:
168+
template <typename MaskDerivedT, typename MaskEnumT,
169+
const llvm::StringLiteral MaskNames[]>
170+
friend class EnumMaskBase;
171+
168172
RawEnumType value_;
169173
};
170174

@@ -175,21 +179,24 @@ class EnumBase : public Printable<DerivedT> {
175179
// raw enum class.
176180
#define CARBON_DEFINE_RAW_ENUM_CLASS(EnumClassName, UnderlyingType) \
177181
namespace Internal { \
178-
extern const llvm::StringLiteral EnumClassName##Names[]; \
179-
enum class EnumClassName##RawEnum : UnderlyingType; \
182+
struct EnumClassName##Data { \
183+
static const llvm::StringLiteral Names[]; \
184+
enum class RawEnum : UnderlyingType; \
185+
}; \
180186
} \
181-
enum class Internal::EnumClassName##RawEnum : UnderlyingType
187+
enum class Internal::EnumClassName##Data::RawEnum : UnderlyingType
182188

183-
// In CARBON_DEFINE_RAW_ENUM_CLASS block, use this to generate each enumerator.
189+
// In the `CARBON_DEFINE_RAW_ENUM_CLASS` block, use this to generate each
190+
// enumerator.
184191
#define CARBON_RAW_ENUM_ENUMERATOR(Name) Name,
185192

186193
// Use this to compute the `Internal::EnumBase` specialization for a Carbon enum
187194
// class. It both computes the name of the raw enum and ensures all the
188195
// namespaces are correct.
189-
#define CARBON_ENUM_BASE(EnumClassName) \
190-
::Carbon::Internal::EnumBase<EnumClassName, \
191-
Internal::EnumClassName##RawEnum, \
192-
Internal::EnumClassName##Names>
196+
#define CARBON_ENUM_BASE(EnumClassName) \
197+
::Carbon::Internal::EnumBase<EnumClassName, \
198+
Internal::EnumClassName##Data::RawEnum, \
199+
Internal::EnumClassName##Data::Names>
193200

194201
// Use this within the Carbon enum class body to generate named constant
195202
// declarations for each value.
@@ -208,7 +215,7 @@ class EnumBase : public Printable<DerivedT> {
208215
// `clang-format` has a bug with spacing around `->` returns in macros. See
209216
// https://bugs.llvm.org/show_bug.cgi?id=48320 for details.
210217
#define CARBON_DEFINE_ENUM_CLASS_NAMES(EnumClassName) \
211-
constexpr llvm::StringLiteral Internal::EnumClassName##Names[]
218+
constexpr llvm::StringLiteral Internal::EnumClassName##Data::Names[]
212219

213220
// Use this within the names array initializer to generate a string for each
214221
// name.

common/enum_mask_base.h

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
5+
#ifndef CARBON_COMMON_ENUM_MASK_BASE_H_
6+
#define CARBON_COMMON_ENUM_MASK_BASE_H_
7+
8+
#include <bit>
9+
10+
#include "common/enum_base.h"
11+
#include "llvm/ADT/StringExtras.h"
12+
13+
namespace Carbon::Internal {
14+
15+
// CRTP-style base class similar to `EnumBase`, but supporting mask enums.
16+
//
17+
// Users must be in the `Carbon` namespace and should look like the following.
18+
//
19+
// In `my_kind.h`:
20+
// ```
21+
// #define CARBON_MY_KIND(X) \
22+
// X(Enumerator1) \
23+
// X(Enumerator2) \
24+
// X(Enumerator3) \
25+
// ...
26+
//
27+
// CARBON_DEFINE_RAW_ENUM_MASK(MyKind) {
28+
// CARBON_MY_KIND(CARBON_RAW_ENUM_MASK_ENUMERATOR)
29+
// };
30+
//
31+
// class MyKind : public CARBON_ENUM_MASK_BASE(MyKind) {
32+
// public:
33+
// CARBON_MY_KIND(CARBON_ENUM_MASK_CONSTANT_DECL)
34+
//
35+
// // Plus, anything else you wish to include.
36+
// };
37+
//
38+
// #define CARBON_MY_KIND_WITH_TYPE(X) \
39+
// CARBON_ENUM_MASK_CONSTANT_DEFINITION(MyKind, X)
40+
// CARBON_MY_KIND(CARBON_MY_KIND_WITH_TYPE)
41+
// #undef CARBON_MY_KIND_WITH_TYPE
42+
// ```
43+
//
44+
// In `my_kind.cpp`:
45+
// ```
46+
// CARBON_DEFINE_ENUM_MASK_NAMES(MyKind) = {
47+
// CARBON_MY_KIND(CARBON_ENUM_MASK_NAME_STRING)};
48+
// ```
49+
template <typename DerivedT, typename EnumT, const llvm::StringLiteral Names[]>
50+
class EnumMaskBase : public EnumBase<DerivedT, EnumT, Names> {
51+
public:
52+
// Provide a standard `None`.
53+
//
54+
// This uses a `&` to trigger slightly different instantiation behaviors in
55+
// Clang. For context on why this is needed, see http://wg21.link/CWG2800.
56+
// NOLINTNEXTLINE(readability-identifier-naming)
57+
static const DerivedT& None;
58+
59+
// Returns true if there's a non-empty set intersection.
60+
constexpr auto HasAnyOf(DerivedT other) const -> bool {
61+
return !(*this & other).empty();
62+
}
63+
64+
// Adds entries to the mask.
65+
auto Add(DerivedT other) -> void { *this = *this | other; }
66+
67+
// Removes entries from the mask.
68+
auto Remove(DerivedT other) -> void { *this = *this & ~other; }
69+
70+
constexpr auto operator|(DerivedT other) const -> DerivedT {
71+
return DerivedT::FromInt(this->AsInt() | other.AsInt());
72+
}
73+
74+
constexpr auto operator&(DerivedT other) const -> DerivedT {
75+
return DerivedT::FromInt(this->AsInt() & other.AsInt());
76+
}
77+
78+
constexpr auto operator~() const -> DerivedT {
79+
return DerivedT::FromInt(~this->AsInt());
80+
}
81+
82+
constexpr auto empty() const -> bool { return this->AsInt() == 0; }
83+
84+
// Returns the name of this value. Requires it to be a single value, not
85+
// combined.
86+
//
87+
// This method will be automatically defined using the static `names` string
88+
// table in the base class, which is in turn will be populated for each
89+
// derived type using the macro helpers in this file.
90+
//
91+
// This shadows EnumBase::name.
92+
auto name() const -> llvm::StringRef {
93+
CARBON_CHECK(std::has_single_bit(this->AsInt()), "Not a single bit: {0}",
94+
this->AsInt());
95+
return Names[std::bit_width(this->AsInt())];
96+
}
97+
98+
// Prints this value as a `|`-separated list of mask entries, or `None`.
99+
//
100+
// This shadows EnumBase::Print.
101+
auto Print(llvm::raw_ostream& out) const -> void {
102+
int value = this->AsInt();
103+
if (value == 0) {
104+
out << "None";
105+
return;
106+
}
107+
llvm::ListSeparator sep("|");
108+
for (int bit = 0; value != 0; value >>= 1, ++bit) {
109+
if (value & 1) {
110+
out << sep << Names[bit];
111+
}
112+
}
113+
}
114+
};
115+
116+
template <typename DerivedT, typename EnumT, const llvm::StringLiteral Names[]>
117+
constexpr const DerivedT& EnumMaskBase<DerivedT, EnumT, Names>::None =
118+
DerivedT::FromInt(0);
119+
120+
} // namespace Carbon::Internal
121+
122+
// Use this before defining a class that derives from `EnumBase` to begin the
123+
// definition of the raw `enum class`. It should be followed by the body of that
124+
// raw enum class.
125+
#define CARBON_DEFINE_RAW_ENUM_MASK(EnumMaskName, UnderlyingType) \
126+
namespace Internal { \
127+
struct EnumMaskName##Data { \
128+
static const llvm::StringLiteral Names[]; \
129+
/* For bit shifts, track the initial counter value. This will increment on \
130+
* each enum entry. */ \
131+
static constexpr uint64_t BitShiftCounter = __COUNTER__ + 1; \
132+
enum class RawEnum : UnderlyingType; \
133+
}; \
134+
} \
135+
enum class Internal::EnumMaskName##Data::RawEnum : UnderlyingType
136+
137+
// In the `CARBON_DEFINE_RAW_ENUM_MASK` block, use this to generate each
138+
// enumerator.
139+
#define CARBON_RAW_ENUM_MASK_ENUMERATOR(Name) \
140+
Name = 1 << (__COUNTER__ - BitShiftCounter),
141+
142+
// Use this to compute the `Internal::EnumMaskBase` specialization for a Carbon
143+
// enum mask. It both computes the name of the raw enum and ensures all the
144+
// namespaces are correct.
145+
#define CARBON_ENUM_MASK_BASE(EnumMaskName) \
146+
::Carbon::Internal::EnumMaskBase<EnumMaskName, \
147+
Internal::EnumMaskName##Data::RawEnum, \
148+
Internal::EnumMaskName##Data::Names>
149+
150+
// Constants and names are declared equivalently as to `EnumBase`.
151+
#define CARBON_ENUM_MASK_CONSTANT_DECL(Name) CARBON_ENUM_CONSTANT_DECL(Name)
152+
#define CARBON_ENUM_MASK_CONSTANT_DEFINITION(EnumMaskName, Name) \
153+
CARBON_ENUM_CONSTANT_DEFINITION(EnumMaskName, Name)
154+
#define CARBON_DEFINE_ENUM_MASK_NAMES(EnumMaskName) \
155+
CARBON_DEFINE_ENUM_CLASS_NAMES(EnumMaskName)
156+
#define CARBON_ENUM_MASK_NAME_STRING(Name) CARBON_ENUM_CLASS_NAME_STRING(Name)
157+
158+
#endif // CARBON_COMMON_ENUM_MASK_BASE_H_

toolchain/check/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ cc_library(
4242
"inst.cpp",
4343
"inst_block_stack.cpp",
4444
"interface.cpp",
45+
"keyword_modifier_set.cpp",
4546
"literal.cpp",
4647
"member_access.cpp",
4748
"merge.cpp",
@@ -118,6 +119,7 @@ cc_library(
118119
"//common:check",
119120
"//common:concepts",
120121
"//common:emplace_by_calling",
122+
"//common:enum_mask_base",
121123
"//common:find",
122124
"//common:growing_range",
123125
"//common:map",

toolchain/check/convert.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,7 @@ static auto CanUseValueOfInitializer(const SemIR::File& sem_ir,
773773
// of an expression of the given category.
774774
static auto CanAddQualifiers(SemIR::TypeQualifiers quals,
775775
SemIR::ExprCategory cat) -> bool {
776-
if (HasTypeQualifier(quals, SemIR::TypeQualifiers::MaybeUnformed) &&
776+
if (quals.HasAnyOf(SemIR::TypeQualifiers::MaybeUnformed) &&
777777
!SemIR::IsRefCategory(cat)) {
778778
// `MaybeUnformed(T)` may have a different value representation or
779779
// initializing representation from `T`, so only allow it to be added for a
@@ -794,21 +794,21 @@ static auto CanAddQualifiers(SemIR::TypeQualifiers quals,
794794
static auto CanRemoveQualifiers(SemIR::TypeQualifiers quals,
795795
SemIR::ExprCategory cat, bool allow_unsafe)
796796
-> bool {
797-
if (HasTypeQualifier(quals, SemIR::TypeQualifiers::Const) && !allow_unsafe &&
797+
if (quals.HasAnyOf(SemIR::TypeQualifiers::Const) && !allow_unsafe &&
798798
SemIR::IsRefCategory(cat)) {
799799
// Removing `const` is an unsafe conversion for a reference expression.
800800
return false;
801801
}
802802

803-
if (HasTypeQualifier(quals, SemIR::TypeQualifiers::Partial) &&
803+
if (quals.HasAnyOf(SemIR::TypeQualifiers::Partial) &&
804804
(!allow_unsafe || cat == SemIR::ExprCategory::Initializing)) {
805805
// TODO: Allow removing `partial` for initializing expressions as a safe
806806
// conversion. `PerformBuiltinConversion` will need to initialize the vptr
807807
// as part of the conversion.
808808
return false;
809809
}
810810

811-
if (HasTypeQualifier(quals, SemIR::TypeQualifiers::MaybeUnformed) &&
811+
if (quals.HasAnyOf(SemIR::TypeQualifiers::MaybeUnformed) &&
812812
(!allow_unsafe || cat == SemIR::ExprCategory::Initializing)) {
813813
// As an unsafe conversion, `MaybeUnformed` can be removed from a value or
814814
// reference expression.

toolchain/check/generic.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323

2424
namespace Carbon::Check {
2525

26+
CARBON_DEFINE_ENUM_MASK_NAMES(DependentInstKind) = {
27+
CARBON_DEPENDENT_INST_KIND(CARBON_ENUM_MASK_NAME_STRING)};
28+
2629
static auto MakeSelfSpecificId(Context& context, SemIR::GenericId generic_id)
2730
-> SemIR::SpecificId;
2831

@@ -302,14 +305,14 @@ auto AttachDependentInstToCurrentGeneric(Context& context,
302305
if (context.generic_region_stack().Empty()) {
303306
// This should only happen for `*Decl` instructions, never for template
304307
// actions.
305-
CARBON_CHECK((dep_kind & DependentInst::Template) == DependentInst::None);
308+
CARBON_CHECK(!dep_kind.HasAnyOf(DependentInstKind::Template));
306309
return;
307310
}
308311

309312
context.generic_region_stack().AddDependentInst(dependent_inst.inst_id);
310313

311314
// If the type is symbolic, replace it with a type specific to this generic.
312-
if ((dep_kind & DependentInst::SymbolicType) != DependentInst::None) {
315+
if (dep_kind.HasAnyOf(DependentInstKind::SymbolicType)) {
313316
auto inst = context.insts().Get(inst_id);
314317
auto type_id = AddGenericTypeToEvalBlock(context, SemIR::LocId(inst_id),
315318
inst.type_id());
@@ -327,15 +330,15 @@ auto AttachDependentInstToCurrentGeneric(Context& context,
327330
// we'll need to evaluate this instruction when forming the specific. Update
328331
// the constant value of the instruction to refer to the result of that
329332
// eventual evaluation.
330-
if ((dep_kind & DependentInst::SymbolicConstant) != DependentInst::None) {
333+
if (dep_kind.HasAnyOf(DependentInstKind::SymbolicConstant)) {
331334
// Update the constant value to refer to this generic.
332335
context.constant_values().Set(
333336
inst_id, AddGenericConstantToEvalBlock(context, inst_id));
334337
}
335338

336339
// If the instruction is a template action, add it directly to this position
337340
// in the eval block.
338-
if ((dep_kind & DependentInst::Template) != DependentInst::None) {
341+
if (dep_kind.HasAnyOf(DependentInstKind::Template)) {
339342
AddTemplateActionToEvalBlock(context, inst_id);
340343
}
341344
}

toolchain/check/generic.h

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,47 @@
55
#ifndef CARBON_TOOLCHAIN_CHECK_GENERIC_H_
66
#define CARBON_TOOLCHAIN_CHECK_GENERIC_H_
77

8-
#include "llvm/ADT/BitmaskEnum.h"
8+
#include "common/enum_mask_base.h"
99
#include "toolchain/check/context.h"
1010
#include "toolchain/sem_ir/entity_with_params_base.h"
1111
#include "toolchain/sem_ir/ids.h"
1212

1313
namespace Carbon::Check {
1414

15-
LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
16-
1715
// Start processing a declaration or definition that might be a generic entity.
1816
auto StartGenericDecl(Context& context) -> void;
1917

2018
// Start processing a declaration or definition that might be a generic entity.
2119
auto StartGenericDefinition(Context& context, SemIR::GenericId generic_id)
2220
-> void;
2321

22+
#define CARBON_DEPENDENT_INST_KIND(X) \
23+
/* The type of the instruction depends on a checked generic parameter. */ \
24+
X(SymbolicType) \
25+
/* The constant value of the instruction depends on a checked generic \
26+
* parameter. */ \
27+
X(SymbolicConstant) \
28+
X(Template)
29+
30+
CARBON_DEFINE_RAW_ENUM_MASK(DependentInstKind, uint8_t) {
31+
CARBON_DEPENDENT_INST_KIND(CARBON_RAW_ENUM_MASK_ENUMERATOR)
32+
};
33+
34+
// Represents a set of keyword modifiers, using a separate bit per modifier.
35+
class DependentInstKind : public CARBON_ENUM_MASK_BASE(DependentInstKind) {
36+
public:
37+
CARBON_DEPENDENT_INST_KIND(CARBON_ENUM_MASK_CONSTANT_DECL)
38+
};
39+
40+
#define CARBON_DEPENDENT_INST_KIND_WITH_TYPE(X) \
41+
CARBON_ENUM_MASK_CONSTANT_DEFINITION(DependentInstKind, X)
42+
CARBON_DEPENDENT_INST_KIND(CARBON_DEPENDENT_INST_KIND_WITH_TYPE)
43+
#undef CARBON_DEPENDENT_INST_KIND_WITH_TYPE
44+
2445
// An instruction that depends on a generic parameter in some way.
2546
struct DependentInst {
26-
// Ways in which an instruction can depend on a generic parameter.
27-
enum Kind : int8_t {
28-
None = 0x0,
29-
// The type of the instruction depends on a checked generic parameter.
30-
SymbolicType = 0x1,
31-
// The constant value of the instruction depends on a checked generic
32-
// parameter.
33-
SymbolicConstant = 0x2,
34-
Template = 0x4,
35-
LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/Template)
36-
};
37-
3847
SemIR::InstId inst_id;
39-
Kind kind;
48+
DependentInstKind kind;
4049
};
4150

4251
// Attach a dependent instruction to the current generic, updating its type and

0 commit comments

Comments
 (0)