gem5 v24.0.0.0
Loading...
Searching...
No Matches
tage_sc_l.cc
Go to the documentation of this file.
1/*
2 * Copyright (c) 2022-2023 The University of Edinburgh
3 * All rights reserved
4 *
5 * The license below extends only to copyright in the software and shall
6 * not be construed as granting a license to any other intellectual
7 * property including but not limited to intellectual property relating
8 * to a hardware implementation of the functionality of the software
9 * licensed hereunder. You may use the software subject to the license
10 * terms below provided that you ensure that this notice is replicated
11 * unmodified and in its entirety in all distributions of the software,
12 * modified or unmodified, in source code or in binary form.
13 *
14 * Copyright (c) 2018 Metempsy Technology Consulting
15 * All rights reserved.
16 *
17 * Copyright (c) 2006 INRIA (Institut National de Recherche en
18 * Informatique et en Automatique / French National Research Institute
19 * for Computer Science and Applied Mathematics)
20 *
21 * All rights reserved.
22 *
23 * Redistribution and use in source and binary forms, with or without
24 * modification, are permitted provided that the following conditions are
25 * met: redistributions of source code must retain the above copyright
26 * notice, this list of conditions and the following disclaimer;
27 * redistributions in binary form must reproduce the above copyright
28 * notice, this list of conditions and the following disclaimer in the
29 * documentation and/or other materials provided with the distribution;
30 * neither the name of the copyright holders nor the names of its
31 * contributors may be used to endorse or promote products derived from
32 * this software without specific prior written permission.
33 *
34 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
35 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
36 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
37 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
38 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
39 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
40 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
41 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
42 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
43 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
44 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45 *
46 * Author: André Seznec, Pau Cabre, Javier Bueno
47 *
48 */
49
50/*
51 * TAGE-SC-L branch predictor base class (devised by Andre Seznec)
52 * It consits of a TAGE + a statistical corrector (SC) + a loop predictor (L)
53 */
54
55#include "cpu/pred/tage_sc_l.hh"
56
57#include "base/random.hh"
58#include "debug/TageSCL.hh"
59
60namespace gem5
61{
62
63namespace branch_prediction
64{
65
66bool
72
73bool
75{
76 return (random_mt.random<int>() & 7) == 0;
77}
78
79TAGE_SC_L::TAGE_SC_L(const TAGE_SC_LParams &p)
80 : LTAGE(p), statisticalCorrector(p.statistical_corrector)
81{
82}
83
86{
87 return new BranchInfo(*this);
88}
89void
91{
92 unsigned numHistLengths = nHistoryTables/2;
94 histLengths[numHistLengths] = maxHist;
95
96 // This calculates the different history lenghts
97 // there are only numHistLengths different lengths
98 // They are initially set to the lower half of histLengths
99 for (int i = 2; i <= numHistLengths; i++) {
100 histLengths[i] = (int) (((double) minHist *
101 pow ((double) (maxHist) / (double) minHist,
102 (double) (i - 1) / (double) ((numHistLengths - 1))))
103 + 0.5);
104 }
105
106 // This copies and duplicates the values from the lower half of the table
107 // Ex: 4, 6, 9, 13 would get exanded to 4, 4, 6, 6, 9, 9, 13, 13
108 for (int i = nHistoryTables; i > 1; i--)
109 {
110 histLengths[i] = histLengths[(i + 1) / 2];
111 }
112
113 for (int i = 1; i <= nHistoryTables; i++)
114 {
115 tagTableTagWidths.push_back(
117
119 }
120}
121
122void
124{
125 // Trick! We only allocate entries for tables 1 and firstLongTagTable and
126 // make the other tables point to these allocated entries
127
131 for (int i = 2; i < firstLongTagTable; ++i) {
132 gtable[i] = gtable[1];
133 }
134 for (int i = firstLongTagTable + 1; i <= nHistoryTables; ++i) {
136 }
137}
138
139void
142{
143 // computes the table addresses and the partial tags
144
145 for (int i = 1; i <= nHistoryTables; i += 2) {
146 tableIndices[i] = gindex(tid, pc, i);
147 tableTags[i] = gtag(tid, pc, i);
148 tableTags[i + 1] = tableTags[i];
149 tableIndices[i + 1] = tableIndices[i] ^
150 (tableTags[i] & ((1 << logTagTableSizes[i]) - 1));
151
152 bi->tableTags[i] = tableTags[i];
153 bi->tableTags[i+1] = tableTags[i+1];
154 }
155
156 Addr t = (pc ^ (threadHistory[tid].pathHist &
157 ((1 << histLengths[firstLongTagTable]) - 1)))
159
160 for (int i = firstLongTagTable; i <= nHistoryTables; i++) {
161 if (noSkip[i]) {
163 bi->tableIndices[i] = tableIndices[i];
164 t++;
166 }
167 }
168
169 t = (pc ^ (threadHistory[tid].pathHist & ((1 << histLengths[1]) - 1)))
171
172 for (int i = 1; i <= firstLongTagTable - 1; i++) {
173 if (noSkip[i]) {
175 bi->tableIndices[i] = tableIndices[i];
176 t++;
178 }
179 }
180}
181
182unsigned
184{
185 BranchInfo *tbi = static_cast<BranchInfo *>(bi);
186 unsigned idx;
187 idx = ((((bi->hitBank-1)/8)<<1)+tbi->altConf) % (numUseAltOnNa-1);
188 return idx;
189}
190
191int
193{
194 int index;
195 int hlen = (histLengths[bank] > pathHistBits) ? pathHistBits :
196 histLengths[bank];
197 unsigned int shortPc = pc;
198
199 // pc is not shifted by instShiftAmt in this implementation
200 index = shortPc ^
201 (shortPc >> ((int) abs(logTagTableSizes[bank] - bank) + 1)) ^
202 threadHistory[tid].computeIndices[bank].comp ^
203 F(threadHistory[tid].pathHist, hlen, bank);
204
205 index = gindex_ext(index, bank);
206
207 return (index & ((1ULL << (logTagTableSizes[bank])) - 1));
208}
209
210int
211TAGE_SC_L_TAGE::F(int a, int size, int bank) const
212{
213 int a1, a2;
214
215 a = a & ((1ULL << size) - 1);
216 a1 = (a & ((1ULL << logTagTableSizes[bank]) - 1));
217 a2 = (a >> logTagTableSizes[bank]);
218
219 if (bank < logTagTableSizes[bank]) {
220 a2 = ((a2 << bank) & ((1ULL << logTagTableSizes[bank]) - 1))
221 + (a2 >> (logTagTableSizes[bank] - bank));
222 }
223
224 a = a1 ^ a2;
225
226 if (bank < logTagTableSizes[bank]) {
227 a = ((a << bank) & ((1ULL << logTagTableSizes[bank]) - 1))
228 + (a >> (logTagTableSizes[bank] - bank));
229 }
230
231 return a;
232}
233
234int
236{
237 return ((pc ^ (pc >> instShiftAmt)) &
238 ((1ULL << (logTagTableSizes[0])) - 1));
239}
240
241void
243 ThreadHistory& tHist, int brtype, bool taken, Addr branch_pc, Addr target)
244{
245 // TAGE update
246 int tmp = ((branch_pc ^ (branch_pc >> instShiftAmt))) ^ taken;
247 int path = branch_pc ^ (branch_pc >> instShiftAmt)
248 ^ (branch_pc >> (instShiftAmt+2));
249 if ((brtype == 3) & taken) {
250 tmp = (tmp ^ (target >> instShiftAmt));
251 path = path ^ (target >> instShiftAmt) ^ (target >> (instShiftAmt+2));
252 }
253
254 // some branch types use 3 bits in global history, the others just 2
255 int maxt = (brtype == 2) ? 3 : 2;
256
257 for (int t = 0; t < maxt; t++) {
258 bool dir = (tmp & 1);
259 tmp >>= 1;
260 int pathbit = (path & 127);
261 path >>= 1;
262 updateGHist(tHist.gHist, dir, tHist.globalHistory, tHist.ptGhist);
263 tHist.pathHist = (tHist.pathHist << 1) ^ pathbit;
264 if (truncatePathHist) {
265 // The 8KB implementation does not do this truncation
266 tHist.pathHist = (tHist.pathHist & ((1ULL << pathHistBits) - 1));
267 }
268 for (int i = 1; i <= nHistoryTables; i++) {
269 tHist.computeIndices[i].update(tHist.gHist);
270 tHist.computeTags[0][i].update(tHist.gHist);
271 tHist.computeTags[1][i].update(tHist.gHist);
272 }
273 }
274}
275
276void
278 ThreadID tid, Addr branch_pc, bool taken, TAGEBase::BranchInfo* b,
279 bool speculative, const StaticInstPtr &inst, Addr target)
280{
281 if (speculative != speculativeHistUpdate) {
282 return;
283 }
284 // speculation is not implemented
285 assert(! speculative);
286
287 ThreadHistory& tHist = threadHistory[tid];
288
289 int brtype = inst->isDirectCtrl() ? 0 : 2;
290 if (! inst->isUncondCtrl()) {
291 ++brtype;
292 }
293 updatePathAndGlobalHistory(tHist, brtype, taken, branch_pc, target);
294
295 DPRINTF(TageSCL, "Updating global histories with branch:%lx; taken?:%d, "
296 "path Hist: %x; pointer:%d\n", branch_pc, taken, tHist.pathHist,
297 tHist.ptGhist);
298}
299
300void
302 Addr target)
303{
304 fatal("Speculation is not implemented");
305}
306
307void
308TAGE_SC_L_TAGE::adjustAlloc(bool & alloc, bool taken, bool pred_taken)
309{
310 // Do not allocate too often if the prediction is ok
311 if ((taken == pred_taken) && ((random_mt.random<int>() & 31) != 0)) {
312 alloc = false;
313 }
314}
315
316int
318{
319 int a = 1;
320 if ((random_mt.random<int>() & 127) < 32) {
321 a = 2;
322 }
323 return ((((bi->hitBank - 1 + 2 * a) & 0xffe)) ^
324 (random_mt.random<int>() & 1));
325}
326
327void
329{
330 //just the best formula for the Championship:
331 //In practice when one out of two entries are useful
332 if (tCounter < 0) {
333 tCounter = 0;
334 }
335
336 if (tCounter >= ((1ULL << logUResetPeriod))) {
337 // Update the u bits for the short tags table
338 for (int j = 0; j < (shortTagsTageFactor*(1<<logTagTableSize)); j++) {
339 resetUctr(gtable[1][j].u);
340 }
341
342 // Update the u bits for the long tags table
343 for (int j = 0; j < (longTagsTageFactor*(1<<logTagTableSize)); j++) {
345 }
346
347 tCounter = 0;
348 }
349}
350
351bool
353{
355 static_cast<TAGE_SC_L_TAGE::BranchInfo *>(tage_bi);
356
357 int bim = (btablePrediction[bi->bimodalIndex] << 1)
359
360 bi->highConf = (bim == 0) || (bim == 3);
361 bi->lowConf = ! bi->highConf;
362 bi->altConf = bi->highConf;
363 bi->medConf = false;
364 return TAGEBase::getBimodePred(pc, tage_bi);
365}
366
367void
369{
370 TAGE_SC_L_TAGE::BranchInfo *tage_scl_bi =
371 static_cast<TAGE_SC_L_TAGE::BranchInfo *>(bi);
372 int8_t ctr = gtable[bi->altBank][bi->altBankIndex].ctr;
373 tage_scl_bi->altConf = (abs(2*ctr + 1) > 1);
374}
375
376bool
377TAGE_SC_L::predict(ThreadID tid, Addr pc, bool cond_branch, void* &b)
378{
382 b = (void*)(bi);
383
384 bool pred_taken = tage->tagePredict(tid, pc, cond_branch,
385 bi->tageBranchInfo);
386 pred_taken = loopPredictor->loopPredict(tid, pc, cond_branch,
387 bi->lpBranchInfo, pred_taken,
389
390 if (bi->lpBranchInfo->loopPredUsed) {
391 bi->tageBranchInfo->provider = LOOP;
392 }
393
394 TAGE_SC_L_TAGE::BranchInfo* tage_scl_bi =
395 static_cast<TAGE_SC_L_TAGE::BranchInfo *>(bi->tageBranchInfo);
396
397 // Copy the confidences computed by TAGE
398 bi->scBranchInfo->lowConf = tage_scl_bi->lowConf;
399 bi->scBranchInfo->highConf = tage_scl_bi->highConf;
400 bi->scBranchInfo->altConf = tage_scl_bi->altConf;
401 bi->scBranchInfo->medConf = tage_scl_bi->medConf;
402
403 bool use_tage_ctr = bi->tageBranchInfo->hitBank > 0;
404 int8_t tage_ctr = use_tage_ctr ?
405 tage->getCtr(tage_scl_bi->hitBank, tage_scl_bi->hitBankIndex) : 0;
406 bool bias = (bi->tageBranchInfo->longestMatchPred !=
407 bi->tageBranchInfo->altTaken);
408
409 pred_taken = statisticalCorrector->scPredict(tid, pc, cond_branch,
410 bi->scBranchInfo, pred_taken, bias, use_tage_ctr, tage_ctr,
411 tage->getTageCtrBits(), bi->tageBranchInfo->hitBank,
412 bi->tageBranchInfo->altBank, tage->getPathHist(tid));
413
414 if (bi->scBranchInfo->usedScPred) {
415 bi->tageBranchInfo->provider = SC;
416 }
417
418 // record final prediction
419 bi->lpBranchInfo->predTaken = pred_taken;
420
421 return pred_taken;
422}
423
424void
425TAGE_SC_L::update(ThreadID tid, Addr pc, bool taken, void *&bp_history,
426 bool squashed, const StaticInstPtr & inst, Addr target)
427{
428 assert(bp_history);
429
430 TageSCLBranchInfo* bi = static_cast<TageSCLBranchInfo*>(bp_history);
432 static_cast<TAGE_SC_L_TAGE::BranchInfo *>(bi->tageBranchInfo);
433
434 if (squashed) {
436 // This restores the global history, then update it
437 // and recomputes the folded histories.
438 tage->squash(tid, taken, tage_bi, target);
439 if (bi->tageBranchInfo->condBranch) {
440 loopPredictor->squashLoop(bi->lpBranchInfo);
441 }
442 }
443 return;
444 }
445
446 int nrand = random_mt.random<int>() & 3;
447 if (tage_bi->condBranch) {
448 DPRINTF(TageSCL, "Updating tables for branch:%lx; taken?:%d\n",
449 pc, taken);
450 tage->updateStats(taken, bi->tageBranchInfo);
451
452 loopPredictor->updateStats(taken, bi->lpBranchInfo);
453
454 statisticalCorrector->updateStats(taken, bi->scBranchInfo);
455
456 bool bias = (bi->tageBranchInfo->longestMatchPred !=
457 bi->tageBranchInfo->altTaken);
459 bi->scBranchInfo, target, bias, bi->tageBranchInfo->hitBank,
460 bi->tageBranchInfo->altBank, tage->getPathHist(tid));
461
462 loopPredictor->condBranchUpdate(tid, pc, taken,
463 bi->tageBranchInfo->tagePred, bi->lpBranchInfo, instShiftAmt);
464
465 tage->condBranchUpdate(tid, pc, taken, bi->tageBranchInfo,
466 nrand, target, bi->lpBranchInfo->predTaken);
467 }
468
471 bi->scBranchInfo, target);
472
473 tage->updateHistories(tid, pc, taken, bi->tageBranchInfo, false,
474 inst, target);
475 }
476
477 delete bi;
478 bp_history = nullptr;
479}
480
481} // namespace branch_prediction
482} // namespace gem5
#define DPRINTF(x,...)
Definition trace.hh:210
static std::stack< std::string > path
Definition serialize.hh:315
bool isDirectCtrl() const
bool isUncondCtrl() const
const unsigned instShiftAmt
Number of bits to shift instructions by for predictor addresses.
LoopPredictor * loopPredictor
The loop predictor object.
Definition ltage.hh:93
void updateStats(bool taken, BranchInfo *bi)
Update the stats.
void condBranchUpdate(ThreadID tid, Addr branch_pc, bool taken, bool tage_pred, BranchInfo *bi, unsigned instShiftAmt)
Update LTAGE for conditional branches.
virtual bool calcConf(int index) const
bool loopPredict(ThreadID tid, Addr branch_pc, bool cond_branch, BranchInfo *bi, bool prev_pred_taken, unsigned instShiftAmt)
Get the loop prediction.
virtual void scHistoryUpdate(Addr branch_pc, const StaticInstPtr &inst, bool taken, BranchInfo *tage_bi, Addr corrTarget)
virtual bool scPredict(ThreadID tid, Addr branch_pc, bool cond_branch, BranchInfo *bi, bool prev_pred_taken, bool bias_bit, bool use_conf_ctr, int8_t conf_ctr, unsigned conf_bits, int hitBank, int altBank, int64_t phist, int init_lsum=0)
virtual void condBranchUpdate(ThreadID tid, Addr branch_pc, bool taken, BranchInfo *bi, Addr corrTarget, bool bias_bit, int hitBank, int altBank, int64_t phist)
virtual void updateHistories(ThreadID tid, Addr branch_pc, bool taken, BranchInfo *b, bool speculative, const StaticInstPtr &inst=nullStaticInstPtr, Addr target=MaxAddr)
(Speculatively) updates global histories (path and direction).
Definition tage_base.cc:588
virtual void resetUctr(uint8_t &u)
Algorithm for resetting a single U counter.
Definition tage_base.cc:508
virtual void updateStats(bool taken, BranchInfo *bi)
Update the stats.
Definition tage_base.cc:662
const unsigned logRatioBiModalHystEntries
Definition tage_base.hh:424
std::vector< bool > btableHysteresis
Definition tage_base.hh:437
void updateGHist(uint8_t *&h, bool dir, uint8_t *tab, int &PT)
(Speculatively) updates the global branch history.
Definition tage_base.cc:322
std::vector< bool > btablePrediction
Definition tage_base.hh:436
std::vector< ThreadHistory > threadHistory
Definition tage_base.hh:464
virtual bool getBimodePred(Addr pc, BranchInfo *bi) const
Get a branch prediction from the bimodal predictor.
Definition tage_base.cc:293
int8_t getCtr(int hitBank, int hitBankIndex) const
Definition tage_base.cc:768
std::vector< int > logTagTableSizes
Definition tage_base.hh:434
virtual void squash(ThreadID tid, bool taken, BranchInfo *bi, Addr target)
Restores speculatively updated path and direction histories.
Definition tage_base.cc:629
bool tagePredict(ThreadID tid, Addr branch_pc, bool cond_branch, BranchInfo *bi)
TAGE prediction called from TAGE::predict.
Definition tage_base.cc:360
int getPathHist(ThreadID tid) const
Definition tage_base.cc:780
std::vector< unsigned > tagTableTagWidths
Definition tage_base.hh:433
virtual void condBranchUpdate(ThreadID tid, Addr branch_pc, bool taken, BranchInfo *bi, int nrand, Addr corrTarget, bool pred, bool preAdjustAlloc=false)
Update TAGE for conditional branches.
Definition tage_base.cc:514
virtual bool optionalAgeInc() const override
Definition tage_sc_l.cc:74
virtual bool calcConf(int index) const override
Definition tage_sc_l.cc:67
void updatePathAndGlobalHistory(ThreadHistory &tHist, int brtype, bool taken, Addr branch_pc, Addr target)
Definition tage_sc_l.cc:242
void extraAltCalc(TAGEBase::BranchInfo *bi) override
Extra steps for calculating altTaken For this base TAGE class it does nothing.
Definition tage_sc_l.cc:368
void calculateIndicesAndTags(ThreadID tid, Addr branch_pc, TAGEBase::BranchInfo *bi) override
On a prediction, calculates the TAGE indices and tags for all the different history lengths.
Definition tage_sc_l.cc:140
int bindex(Addr pc_in) const override
Computes the index used to access the bimodal table.
Definition tage_sc_l.cc:235
int calcDep(TAGEBase::BranchInfo *bi)
Definition tage_sc_l.cc:317
bool getBimodePred(Addr branch_pc, TAGEBase::BranchInfo *tage_bi) const override
Get a branch prediction from the bimodal predictor.
Definition tage_sc_l.cc:352
virtual TAGEBase::BranchInfo * makeBranchInfo() override
Definition tage_sc_l.cc:85
int gindex(ThreadID tid, Addr pc, int bank) const override
Computes the index used to access a partially tagged table.
Definition tage_sc_l.cc:192
virtual int gindex_ext(int index, int bank) const =0
int F(int phist, int size, int bank) const override
Utility function to shuffle the path history depending on which tagged table we are accessing.
Definition tage_sc_l.cc:211
void calculateParameters() override
Calculates the history lengths and some other paramters in derived classes.
Definition tage_sc_l.cc:90
void squash(ThreadID tid, bool taken, TAGEBase::BranchInfo *bi, Addr target) override
Restores speculatively updated path and direction histories.
Definition tage_sc_l.cc:301
void adjustAlloc(bool &alloc, bool taken, bool pred_taken) override
Extra calculation to tell whether TAGE allocaitons may happen or not on an update For this base TAGE ...
Definition tage_sc_l.cc:308
void buildTageTables() override
Instantiates the TAGE table entries.
Definition tage_sc_l.cc:123
unsigned getUseAltIdx(TAGEBase::BranchInfo *bi, Addr branch_pc) override
Calculation of the index for useAltPredForNewlyAllocated On this base TAGE implementation it is alway...
Definition tage_sc_l.cc:183
virtual uint16_t gtag(ThreadID tid, Addr pc, int bank) const override=0
Computes the partial tag of a tagged table.
void updateHistories(ThreadID tid, Addr branch_pc, bool taken, TAGEBase::BranchInfo *b, bool speculative, const StaticInstPtr &inst, Addr target) override
(Speculatively) updates global histories (path and direction).
Definition tage_sc_l.cc:277
void handleUReset() override
Handles the U bits reset.
Definition tage_sc_l.cc:328
bool predict(ThreadID tid, Addr pc, bool cond_branch, void *&b) override
Get a branch prediction from LTAGE.
Definition tage_sc_l.cc:377
void update(ThreadID tid, Addr pc, bool taken, void *&bp_history, bool squashed, const StaticInstPtr &inst, Addr target) override
Updates the BP with taken/not taken information.
Definition tage_sc_l.cc:425
TAGE_SC_L(const TAGE_SC_LParams &params)
Definition tage_sc_l.cc:79
StatisticalCorrector * statisticalCorrector
Definition tage_sc_l.hh:170
Random random_mt
Definition random.cc:99
std::enable_if_t< std::is_integral_v< T >, T > random()
Use the SFINAE idiom to choose an implementation based on whether the type is integral or floating po...
Definition random.hh:90
#define fatal(...)
This implements a cprintf based fatal() function.
Definition logging.hh:200
Bitfield< 22 > a1
Bitfield< 5 > t
Definition misc_types.hh:71
Bitfield< 7 > b
Bitfield< 7 > i
Definition misc_types.hh:67
Bitfield< 20 > tbi
Bitfield< 8 > a
Definition misc_types.hh:66
Bitfield< 22 > u
Bitfield< 4 > pc
Bitfield< 30, 0 > index
Bitfield< 0 > p
Bitfield< 20, 16 > bi
Definition types.hh:80
Copyright (c) 2024 - Pranith Kumar Copyright (c) 2020 Inria All rights reserved.
Definition binary32.hh:36
int16_t ThreadID
Thread index/ID type.
Definition types.hh:235
uint64_t Addr
Address type This will probably be moved somewhere else in the near future.
Definition types.hh:147

Generated on Tue Jun 18 2024 16:24:02 for gem5 by doxygen 1.11.0