@@ -32,6 +32,81 @@ class ValueStoreNotPrintable {};
32
32
33
33
} // namespace Internal
34
34
35
+ struct IdTag {
36
+ IdTag ()
37
+ : id_tag_(0 ),
38
+ initial_reserved_ids_ (std::numeric_limits<int32_t >::max()) {}
39
+ explicit IdTag (int32_t id_index, int32_t initial_reserved_ids)
40
+ : initial_reserved_ids_(initial_reserved_ids) {
41
+ // Shift down by 1 to get out of the high bit to avoid using any negative
42
+ // ids, since they have special uses.
43
+ // Shift down by another 1 to free up the second highest bit for a marker to
44
+ // indicate whether the index is tagged (& needs to be untagged) or not.
45
+ // Add one to the index so it's not zero-based, to make it a bit less likely
46
+ // this doesn't collide with anything else (though with the
47
+ // second-highest-bit-tagging this might not be needed).
48
+ id_tag_ = llvm::reverseBits ((((id_index + 1 ) << 1 ) | 1 ) << 1 );
49
+ }
50
+ auto GetCheckIRId () const -> int32_t {
51
+ return (llvm::reverseBits (id_tag_) >> 2 ) - 1 ;
52
+ }
53
+ auto Apply (int32_t index) const -> int32_t {
54
+ if (index < initial_reserved_ids_) {
55
+ return index;
56
+ }
57
+ // assert that id_tag_ doesn't have the second highest bit set
58
+ return index ^ id_tag_;
59
+ }
60
+ static auto DecomposeWithBestEffort (int32_t tagged_index)
61
+ -> std::pair<int32_t, int32_t> {
62
+ if (tagged_index < 0 ) {
63
+ return {-1 , tagged_index};
64
+ }
65
+ if ((llvm::reverseBits (2 ) & tagged_index) == 0 ) {
66
+ return {-1 , tagged_index};
67
+ }
68
+ int length = 0 ;
69
+ int location = 0 ;
70
+ for (int i = 0 ; i != 32 ; ++i) {
71
+ int current_run = 0 ;
72
+ int location_of_current_run = i;
73
+ while (i != 32 && (tagged_index & (1 << i)) == 0 ) {
74
+ ++current_run;
75
+ ++i;
76
+ }
77
+ if (current_run != 0 ) {
78
+ --i;
79
+ }
80
+ if (current_run > length) {
81
+ length = current_run;
82
+ location = location_of_current_run;
83
+ }
84
+ }
85
+ if (length < 8 ) {
86
+ return {-1 , tagged_index};
87
+ }
88
+ auto index_mask = llvm::maskTrailingOnes<uint32_t >(location);
89
+ auto ir_id = (llvm::reverseBits (tagged_index & ~index_mask) >> 2 ) - 1 ;
90
+ auto index = tagged_index & index_mask;
91
+ return {ir_id, index};
92
+ }
93
+ auto Remove (int32_t tagged_index) const -> int32_t {
94
+ if ((llvm::reverseBits (2 ) & tagged_index) == 0 ) {
95
+ CARBON_DCHECK (tagged_index < initial_reserved_ids_,
96
+ " This untagged index is outside the initial reserved ids "
97
+ " and should have been tagged." );
98
+ return tagged_index;
99
+ }
100
+ auto index = tagged_index ^ id_tag_;
101
+ CARBON_DCHECK (index >= initial_reserved_ids_,
102
+ " When removing tagging bits, found an index that "
103
+ " shouldn't've been tagged in the first place." );
104
+ return index;
105
+ }
106
+ int32_t id_tag_;
107
+ int32_t initial_reserved_ids_;
108
+ };
109
+
35
110
// A simple wrapper for accumulating values, providing IDs to later retrieve the
36
111
// value. This does not do deduplication.
37
112
template <typename IdT, typename ValueT>
@@ -74,6 +149,7 @@ class ValueStore
74
149
};
75
150
76
151
ValueStore () = default ;
152
+ explicit ValueStore (IdTag tag) : tag_(tag) {}
77
153
78
154
// Stores the value and returns an ID to reference it.
79
155
auto Add (ValueType value) -> IdType {
@@ -82,8 +158,8 @@ class ValueStore
82
158
// tracking down issues easier.
83
159
CARBON_DCHECK (size_ < std::numeric_limits<int32_t >::max (), " Id overflow" );
84
160
85
- IdType id (size_);
86
- auto [chunk_index, pos] = IdToChunkIndices (id );
161
+ IdType id (tag_. Apply ( size_) );
162
+ auto [chunk_index, pos] = RawIndexToChunkIndices (size_ );
87
163
++size_;
88
164
89
165
CARBON_DCHECK (static_cast <size_t >(chunk_index) <= chunks_.size (),
@@ -99,16 +175,12 @@ class ValueStore
99
175
100
176
// Returns a mutable value for an ID.
101
177
auto Get (IdType id) -> RefType {
102
- CARBON_DCHECK (id.index >= 0 , " {0}" , id);
103
- CARBON_DCHECK (id.index < size_, " {0}" , id);
104
178
auto [chunk_index, pos] = IdToChunkIndices (id);
105
179
return chunks_[chunk_index].Get (pos);
106
180
}
107
181
108
182
// Returns the value for an ID.
109
183
auto Get (IdType id) const -> ConstRefType {
110
- CARBON_DCHECK (id.index >= 0 , " {0}" , id);
111
- CARBON_DCHECK (id.index < size_, " {0}" , id);
112
184
auto [chunk_index, pos] = IdToChunkIndices (id);
113
185
return chunks_[chunk_index].Get (pos);
114
186
}
@@ -118,7 +190,7 @@ class ValueStore
118
190
if (size <= size_) {
119
191
return ;
120
192
}
121
- auto [final_chunk_index, _] = IdToChunkIndices ( IdType ( size - 1 ) );
193
+ auto [final_chunk_index, _] = RawIndexToChunkIndices ( size - 1 );
122
194
chunks_.resize (final_chunk_index + 1 );
123
195
}
124
196
@@ -128,10 +200,10 @@ class ValueStore
128
200
return ;
129
201
}
130
202
131
- auto [begin_chunk_index, begin_pos] = IdToChunkIndices ( IdType ( size_) );
203
+ auto [begin_chunk_index, begin_pos] = RawIndexToChunkIndices ( size_);
132
204
// Use an inclusive range so that if `size` would be the next chunk, we
133
205
// don't try doing something with it.
134
- auto [end_chunk_index, end_pos] = IdToChunkIndices ( IdType ( size - 1 ) );
206
+ auto [end_chunk_index, end_pos] = RawIndexToChunkIndices ( size - 1 );
135
207
chunks_.resize (end_chunk_index + 1 );
136
208
137
209
// If the begin and end chunks are the same, we only fill from begin to end.
@@ -192,13 +264,33 @@ class ValueStore
192
264
// `mapped_iterator` incorrectly infers the pointer type for `PointerProxy`.
193
265
// NOLINTNEXTLINE(readability-const-return-type)
194
266
auto index_to_id = [&](int32_t i) -> const std::pair<IdType, ConstRefType> {
195
- return std::pair<IdType, ConstRefType>(IdType (i), Get (IdType (i)));
267
+ IdType id (tag_.Apply (i));
268
+ return std::pair<IdType, ConstRefType>(id, Get (id));
196
269
};
197
270
// Because indices into `ValueStore` are all sequential values from 0, we
198
271
// can use llvm::seq to walk all indices in the store.
199
272
return llvm::map_range (llvm::seq (size_), index_to_id);
200
273
}
201
274
275
+ auto GetIdTag () const -> IdTag { return tag_; }
276
+ auto GetRawIndex (IdT id) const -> int32_t {
277
+ auto index = tag_.Remove (id.index );
278
+ CARBON_DCHECK (index >= 0 , " {0}" , index);
279
+ // Attempt to decompose id.index to include extra detail in the check here
280
+ #ifndef NDEBUG
281
+ if (index >= size_) {
282
+ auto [ir_id, decomposed_index] = IdTag::DecomposeWithBestEffort (id.index );
283
+ CARBON_DCHECK (
284
+ index < size_,
285
+ " Untagged index was outside of container range. Possibly tagged "
286
+ " index {0}. Best-effort decomposition: CheckIRId: {1}, index: {2}. "
287
+ " The IdTag for this container is: {3}" ,
288
+ id.index , ir_id, decomposed_index, tag_.GetCheckIRId ());
289
+ }
290
+ #endif
291
+ return index;
292
+ }
293
+
202
294
private:
203
295
// A chunk of `ValueType`s which has a fixed capacity, but variable size.
204
296
// Tracks the size internally for verifying bounds.
@@ -312,9 +404,10 @@ class ValueStore
312
404
int32_t num_ = 0 ;
313
405
};
314
406
315
- // Converts an id into an index into the set of chunks, and an offset into
316
- // that specific chunk. Looks for index overflow in non-optimized builds.
317
- static auto IdToChunkIndices (IdType id) -> std::pair<int32_t, int32_t> {
407
+ // Converts a raw index into an index into the set of chunks, and an offset
408
+ // into that specific chunk. Looks for index overflow in non-optimized builds.
409
+ static auto RawIndexToChunkIndices (int32_t index)
410
+ -> std::pair<int32_t, int32_t> {
318
411
constexpr auto LowBits = Chunk::IndexBits ();
319
412
320
413
// Verify there are no unused bits when indexing up to the `Capacity`. This
@@ -328,16 +421,24 @@ class ValueStore
328
421
static_assert (LowBits < 30 );
329
422
330
423
// The index of the chunk is the high bits.
331
- auto chunk = id. index >> LowBits;
424
+ auto chunk = index >> LowBits;
332
425
// The index into the chunk is the low bits.
333
- auto pos = id. index & ((1 << LowBits) - 1 );
426
+ auto pos = index & ((1 << LowBits) - 1 );
334
427
return {chunk, pos};
335
428
}
336
429
430
+ // Converts an id into an index into the set of chunks, and an offset into
431
+ // that specific chunk.
432
+ auto IdToChunkIndices (IdType id) const -> std::pair<int32_t, int32_t> {
433
+ return RawIndexToChunkIndices (GetRawIndex (id));
434
+ }
435
+
337
436
// Number of elements added to the store. The number should never exceed what
338
437
// fits in an `int32_t`, which is checked in non-optimized builds in Add().
339
438
int32_t size_ = 0 ;
340
439
440
+ IdTag tag_;
441
+
341
442
// Storage for the `ValueType` objects, indexed by the id. We use a vector of
342
443
// chunks of `ValueType` instead of just a vector of `ValueType` so that
343
444
// addresses of `ValueType` objects are stable. This allows the rest of the
0 commit comments