Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0
2#include <stddef.h>
3#include <stdlib.h>
4#include <string.h>
5#include <errno.h>
6#include <sys/types.h>
7#include <sys/stat.h>
8#include <unistd.h>
9#include <api/fs/fs.h>
10#include <linux/kernel.h>
11#include "map_symbol.h"
12#include "mem-events.h"
13#include "mem-info.h"
14#include "debug.h"
15#include "evsel.h"
16#include "symbol.h"
17#include "pmu.h"
18#include "pmus.h"
19
20unsigned int perf_mem_events__loads_ldlat = 30;
21
22#define E(t, n, s, l, a) { .tag = t, .name = n, .event_name = s, .ldlat = l, .aux_event = a }
23
24struct perf_mem_event perf_mem_events[PERF_MEM_EVENTS__MAX] = {
25 E("ldlat-loads", "%s/mem-loads,ldlat=%u/P", "mem-loads", true, 0),
26 E("ldlat-stores", "%s/mem-stores/P", "mem-stores", false, 0),
27 E(NULL, NULL, NULL, false, 0),
28};
29#undef E
30
31static char mem_loads_name[100];
32static char mem_stores_name[100];
33
34struct perf_mem_event *perf_pmu__mem_events_ptr(struct perf_pmu *pmu, int i)
35{
36 if (i >= PERF_MEM_EVENTS__MAX || !pmu)
37 return NULL;
38
39 return &pmu->mem_events[i];
40}
41
42static struct perf_pmu *perf_pmus__scan_mem(struct perf_pmu *pmu)
43{
44 while ((pmu = perf_pmus__scan(pmu)) != NULL) {
45 if (pmu->mem_events)
46 return pmu;
47 }
48 return NULL;
49}
50
51struct perf_pmu *perf_mem_events_find_pmu(void)
52{
53 /*
54 * The current perf mem doesn't support per-PMU configuration.
55 * The exact same configuration is applied to all the
56 * mem_events supported PMUs.
57 * Return the first mem_events supported PMU.
58 *
59 * Notes: The only case which may support multiple mem_events
60 * supported PMUs is Intel hybrid. The exact same mem_events
61 * is shared among the PMUs. Only configure the first PMU
62 * is good enough as well.
63 */
64 return perf_pmus__scan_mem(NULL);
65}
66
67/**
68 * perf_pmu__mem_events_num_mem_pmus - Get the number of mem PMUs since the given pmu
69 * @pmu: Start pmu. If it's NULL, search the entire PMU list.
70 */
71int perf_pmu__mem_events_num_mem_pmus(struct perf_pmu *pmu)
72{
73 int num = 0;
74
75 while ((pmu = perf_pmus__scan_mem(pmu)) != NULL)
76 num++;
77
78 return num;
79}
80
81static const char *perf_pmu__mem_events_name(int i, struct perf_pmu *pmu)
82{
83 struct perf_mem_event *e;
84
85 if (i >= PERF_MEM_EVENTS__MAX || !pmu)
86 return NULL;
87
88 e = &pmu->mem_events[i];
89 if (!e)
90 return NULL;
91
92 if (i == PERF_MEM_EVENTS__LOAD || i == PERF_MEM_EVENTS__LOAD_STORE) {
93 if (e->ldlat) {
94 if (!e->aux_event) {
95 /* ARM and Most of Intel */
96 scnprintf(mem_loads_name, sizeof(mem_loads_name),
97 e->name, pmu->name,
98 perf_mem_events__loads_ldlat);
99 } else {
100 /* Intel with mem-loads-aux event */
101 scnprintf(mem_loads_name, sizeof(mem_loads_name),
102 e->name, pmu->name, pmu->name,
103 perf_mem_events__loads_ldlat);
104 }
105 } else {
106 if (!e->aux_event) {
107 /* AMD and POWER */
108 scnprintf(mem_loads_name, sizeof(mem_loads_name),
109 e->name, pmu->name);
110 } else
111 return NULL;
112 }
113
114 return mem_loads_name;
115 }
116
117 if (i == PERF_MEM_EVENTS__STORE) {
118 scnprintf(mem_stores_name, sizeof(mem_stores_name),
119 e->name, pmu->name);
120 return mem_stores_name;
121 }
122
123 return NULL;
124}
125
126bool is_mem_loads_aux_event(struct evsel *leader)
127{
128 struct perf_pmu *pmu = leader->pmu;
129 struct perf_mem_event *e;
130
131 if (!pmu || !pmu->mem_events)
132 return false;
133
134 e = &pmu->mem_events[PERF_MEM_EVENTS__LOAD];
135 if (!e->aux_event)
136 return false;
137
138 return leader->core.attr.config == e->aux_event;
139}
140
141int perf_pmu__mem_events_parse(struct perf_pmu *pmu, const char *str)
142{
143 char *tok, *saveptr = NULL;
144 bool found = false;
145 char *buf;
146 int j;
147
148 /* We need buffer that we know we can write to. */
149 buf = malloc(strlen(str) + 1);
150 if (!buf)
151 return -ENOMEM;
152
153 strcpy(buf, str);
154
155 tok = strtok_r((char *)buf, ",", &saveptr);
156
157 while (tok) {
158 for (j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
159 struct perf_mem_event *e = perf_pmu__mem_events_ptr(pmu, j);
160
161 if (!e->tag)
162 continue;
163
164 if (strstr(e->tag, tok))
165 e->record = found = true;
166 }
167
168 tok = strtok_r(NULL, ",", &saveptr);
169 }
170
171 free(buf);
172
173 if (found)
174 return 0;
175
176 pr_err("failed: event '%s' not found, use '-e list' to get list of available events\n", str);
177 return -1;
178}
179
180static bool perf_pmu__mem_events_supported(const char *mnt, struct perf_pmu *pmu,
181 struct perf_mem_event *e)
182{
183 char path[PATH_MAX];
184 struct stat st;
185
186 if (!e->event_name)
187 return true;
188
189 scnprintf(path, PATH_MAX, "%s/devices/%s/events/%s", mnt, pmu->name, e->event_name);
190
191 return !stat(path, &st);
192}
193
194int perf_pmu__mem_events_init(struct perf_pmu *pmu)
195{
196 const char *mnt = sysfs__mount();
197 bool found = false;
198 int j;
199
200 if (!mnt)
201 return -ENOENT;
202
203 for (j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
204 struct perf_mem_event *e = perf_pmu__mem_events_ptr(pmu, j);
205
206 /*
207 * If the event entry isn't valid, skip initialization
208 * and "e->supported" will keep false.
209 */
210 if (!e->tag)
211 continue;
212
213 e->supported |= perf_pmu__mem_events_supported(mnt, pmu, e);
214 if (e->supported)
215 found = true;
216 }
217
218 return found ? 0 : -ENOENT;
219}
220
221void perf_pmu__mem_events_list(struct perf_pmu *pmu)
222{
223 int j;
224
225 for (j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
226 struct perf_mem_event *e = perf_pmu__mem_events_ptr(pmu, j);
227
228 fprintf(stderr, "%-*s%-*s%s",
229 e->tag ? 13 : 0,
230 e->tag ? : "",
231 e->tag && verbose > 0 ? 25 : 0,
232 e->tag && verbose > 0 ? perf_pmu__mem_events_name(j, pmu) : "",
233 e->supported ? ": available\n" : "");
234 }
235}
236
237int perf_mem_events__record_args(const char **rec_argv, int *argv_nr)
238{
239 const char *mnt = sysfs__mount();
240 struct perf_pmu *pmu = NULL;
241 struct perf_mem_event *e;
242 int i = *argv_nr;
243 const char *s;
244 char *copy;
245
246 while ((pmu = perf_pmus__scan_mem(pmu)) != NULL) {
247 for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
248 e = perf_pmu__mem_events_ptr(pmu, j);
249
250 if (!e->record)
251 continue;
252
253 if (!e->supported) {
254 pr_err("failed: event '%s' not supported\n",
255 perf_pmu__mem_events_name(j, pmu));
256 return -1;
257 }
258
259 s = perf_pmu__mem_events_name(j, pmu);
260 if (!s || !perf_pmu__mem_events_supported(mnt, pmu, e))
261 continue;
262
263 copy = strdup(s);
264 if (!copy)
265 return -1;
266
267 rec_argv[i++] = "-e";
268 rec_argv[i++] = copy;
269 }
270 }
271
272 *argv_nr = i;
273 return 0;
274}
275
276static const char * const tlb_access[] = {
277 "N/A",
278 "HIT",
279 "MISS",
280 "L1",
281 "L2",
282 "Walker",
283 "Fault",
284};
285
286int perf_mem__tlb_scnprintf(char *out, size_t sz, const struct mem_info *mem_info)
287{
288 size_t l = 0, i;
289 u64 m = PERF_MEM_TLB_NA;
290 u64 hit, miss;
291
292 sz -= 1; /* -1 for null termination */
293 out[0] = '\0';
294
295 if (mem_info)
296 m = mem_info__const_data_src(mem_info)->mem_dtlb;
297
298 hit = m & PERF_MEM_TLB_HIT;
299 miss = m & PERF_MEM_TLB_MISS;
300
301 /* already taken care of */
302 m &= ~(PERF_MEM_TLB_HIT|PERF_MEM_TLB_MISS);
303
304 for (i = 0; m && i < ARRAY_SIZE(tlb_access); i++, m >>= 1) {
305 if (!(m & 0x1))
306 continue;
307 if (l) {
308 strcat(out, " or ");
309 l += 4;
310 }
311 l += scnprintf(out + l, sz - l, tlb_access[i]);
312 }
313 if (*out == '\0')
314 l += scnprintf(out, sz - l, "N/A");
315 if (hit)
316 l += scnprintf(out + l, sz - l, " hit");
317 if (miss)
318 l += scnprintf(out + l, sz - l, " miss");
319
320 return l;
321}
322
323static const char * const mem_lvl[] = {
324 "N/A",
325 "HIT",
326 "MISS",
327 "L1",
328 "LFB/MAB",
329 "L2",
330 "L3",
331 "Local RAM",
332 "Remote RAM (1 hop)",
333 "Remote RAM (2 hops)",
334 "Remote Cache (1 hop)",
335 "Remote Cache (2 hops)",
336 "I/O",
337 "Uncached",
338};
339
340static const char * const mem_lvlnum[] = {
341 [PERF_MEM_LVLNUM_UNC] = "Uncached",
342 [PERF_MEM_LVLNUM_CXL] = "CXL",
343 [PERF_MEM_LVLNUM_IO] = "I/O",
344 [PERF_MEM_LVLNUM_ANY_CACHE] = "Any cache",
345 [PERF_MEM_LVLNUM_LFB] = "LFB/MAB",
346 [PERF_MEM_LVLNUM_RAM] = "RAM",
347 [PERF_MEM_LVLNUM_PMEM] = "PMEM",
348 [PERF_MEM_LVLNUM_NA] = "N/A",
349};
350
351static const char * const mem_hops[] = {
352 "N/A",
353 /*
354 * While printing, 'Remote' will be added to represent
355 * 'Remote core, same node' accesses as remote field need
356 * to be set with mem_hops field.
357 */
358 "core, same node",
359 "node, same socket",
360 "socket, same board",
361 "board",
362};
363
364static int perf_mem__op_scnprintf(char *out, size_t sz, const struct mem_info *mem_info)
365{
366 u64 op = PERF_MEM_LOCK_NA;
367 int l;
368
369 if (mem_info)
370 op = mem_info__const_data_src(mem_info)->mem_op;
371
372 if (op & PERF_MEM_OP_NA)
373 l = scnprintf(out, sz, "N/A");
374 else if (op & PERF_MEM_OP_LOAD)
375 l = scnprintf(out, sz, "LOAD");
376 else if (op & PERF_MEM_OP_STORE)
377 l = scnprintf(out, sz, "STORE");
378 else if (op & PERF_MEM_OP_PFETCH)
379 l = scnprintf(out, sz, "PFETCH");
380 else if (op & PERF_MEM_OP_EXEC)
381 l = scnprintf(out, sz, "EXEC");
382 else
383 l = scnprintf(out, sz, "No");
384
385 return l;
386}
387
388int perf_mem__lvl_scnprintf(char *out, size_t sz, const struct mem_info *mem_info)
389{
390 union perf_mem_data_src data_src;
391 int printed = 0;
392 size_t l = 0;
393 size_t i;
394 int lvl;
395 char hit_miss[5] = {0};
396
397 sz -= 1; /* -1 for null termination */
398 out[0] = '\0';
399
400 if (!mem_info)
401 goto na;
402
403 data_src = *mem_info__const_data_src(mem_info);
404
405 if (data_src.mem_lvl & PERF_MEM_LVL_HIT)
406 memcpy(hit_miss, "hit", 3);
407 else if (data_src.mem_lvl & PERF_MEM_LVL_MISS)
408 memcpy(hit_miss, "miss", 4);
409
410 lvl = data_src.mem_lvl_num;
411 if (lvl && lvl != PERF_MEM_LVLNUM_NA) {
412 if (data_src.mem_remote) {
413 strcat(out, "Remote ");
414 l += 7;
415 }
416
417 if (data_src.mem_hops)
418 l += scnprintf(out + l, sz - l, "%s ", mem_hops[data_src.mem_hops]);
419
420 if (mem_lvlnum[lvl])
421 l += scnprintf(out + l, sz - l, mem_lvlnum[lvl]);
422 else
423 l += scnprintf(out + l, sz - l, "L%d", lvl);
424
425 l += scnprintf(out + l, sz - l, " %s", hit_miss);
426 return l;
427 }
428
429 lvl = data_src.mem_lvl;
430 if (!lvl)
431 goto na;
432
433 lvl &= ~(PERF_MEM_LVL_NA | PERF_MEM_LVL_HIT | PERF_MEM_LVL_MISS);
434 if (!lvl)
435 goto na;
436
437 for (i = 0; lvl && i < ARRAY_SIZE(mem_lvl); i++, lvl >>= 1) {
438 if (!(lvl & 0x1))
439 continue;
440 if (printed++) {
441 strcat(out, " or ");
442 l += 4;
443 }
444 l += scnprintf(out + l, sz - l, mem_lvl[i]);
445 }
446
447 if (printed) {
448 l += scnprintf(out + l, sz - l, " %s", hit_miss);
449 return l;
450 }
451
452na:
453 strcat(out, "N/A");
454 return 3;
455}
456
457static const char * const snoop_access[] = {
458 "N/A",
459 "None",
460 "Hit",
461 "Miss",
462 "HitM",
463};
464
465static const char * const snoopx_access[] = {
466 "Fwd",
467 "Peer",
468};
469
470int perf_mem__snp_scnprintf(char *out, size_t sz, const struct mem_info *mem_info)
471{
472 size_t i, l = 0;
473 u64 m = PERF_MEM_SNOOP_NA;
474
475 sz -= 1; /* -1 for null termination */
476 out[0] = '\0';
477
478 if (mem_info)
479 m = mem_info__const_data_src(mem_info)->mem_snoop;
480
481 for (i = 0; m && i < ARRAY_SIZE(snoop_access); i++, m >>= 1) {
482 if (!(m & 0x1))
483 continue;
484 if (l) {
485 strcat(out, " or ");
486 l += 4;
487 }
488 l += scnprintf(out + l, sz - l, snoop_access[i]);
489 }
490
491 m = 0;
492 if (mem_info)
493 m = mem_info__const_data_src(mem_info)->mem_snoopx;
494
495 for (i = 0; m && i < ARRAY_SIZE(snoopx_access); i++, m >>= 1) {
496 if (!(m & 0x1))
497 continue;
498
499 if (l) {
500 strcat(out, " or ");
501 l += 4;
502 }
503 l += scnprintf(out + l, sz - l, snoopx_access[i]);
504 }
505
506 if (*out == '\0')
507 l += scnprintf(out, sz - l, "N/A");
508
509 return l;
510}
511
512int perf_mem__lck_scnprintf(char *out, size_t sz, const struct mem_info *mem_info)
513{
514 u64 mask = PERF_MEM_LOCK_NA;
515 int l;
516
517 if (mem_info)
518 mask = mem_info__const_data_src(mem_info)->mem_lock;
519
520 if (mask & PERF_MEM_LOCK_NA)
521 l = scnprintf(out, sz, "N/A");
522 else if (mask & PERF_MEM_LOCK_LOCKED)
523 l = scnprintf(out, sz, "Yes");
524 else
525 l = scnprintf(out, sz, "No");
526
527 return l;
528}
529
530int perf_mem__blk_scnprintf(char *out, size_t sz, const struct mem_info *mem_info)
531{
532 size_t l = 0;
533 u64 mask = PERF_MEM_BLK_NA;
534
535 sz -= 1; /* -1 for null termination */
536 out[0] = '\0';
537
538 if (mem_info)
539 mask = mem_info__const_data_src(mem_info)->mem_blk;
540
541 if (!mask || (mask & PERF_MEM_BLK_NA)) {
542 l += scnprintf(out + l, sz - l, " N/A");
543 return l;
544 }
545 if (mask & PERF_MEM_BLK_DATA)
546 l += scnprintf(out + l, sz - l, " Data");
547 if (mask & PERF_MEM_BLK_ADDR)
548 l += scnprintf(out + l, sz - l, " Addr");
549
550 return l;
551}
552
553int perf_script__meminfo_scnprintf(char *out, size_t sz, const struct mem_info *mem_info)
554{
555 int i = 0;
556
557 i += scnprintf(out, sz, "|OP ");
558 i += perf_mem__op_scnprintf(out + i, sz - i, mem_info);
559 i += scnprintf(out + i, sz - i, "|LVL ");
560 i += perf_mem__lvl_scnprintf(out + i, sz, mem_info);
561 i += scnprintf(out + i, sz - i, "|SNP ");
562 i += perf_mem__snp_scnprintf(out + i, sz - i, mem_info);
563 i += scnprintf(out + i, sz - i, "|TLB ");
564 i += perf_mem__tlb_scnprintf(out + i, sz - i, mem_info);
565 i += scnprintf(out + i, sz - i, "|LCK ");
566 i += perf_mem__lck_scnprintf(out + i, sz - i, mem_info);
567 i += scnprintf(out + i, sz - i, "|BLK ");
568 i += perf_mem__blk_scnprintf(out + i, sz - i, mem_info);
569
570 return i;
571}
572
573int c2c_decode_stats(struct c2c_stats *stats, struct mem_info *mi)
574{
575 union perf_mem_data_src *data_src = mem_info__data_src(mi);
576 u64 daddr = mem_info__daddr(mi)->addr;
577 u64 op = data_src->mem_op;
578 u64 lvl = data_src->mem_lvl;
579 u64 snoop = data_src->mem_snoop;
580 u64 snoopx = data_src->mem_snoopx;
581 u64 lock = data_src->mem_lock;
582 u64 blk = data_src->mem_blk;
583 /*
584 * Skylake might report unknown remote level via this
585 * bit, consider it when evaluating remote HITMs.
586 *
587 * Incase of power, remote field can also be used to denote cache
588 * accesses from the another core of same node. Hence, setting
589 * mrem only when HOPS is zero along with set remote field.
590 */
591 bool mrem = (data_src->mem_remote && !data_src->mem_hops);
592 int err = 0;
593
594#define HITM_INC(__f) \
595do { \
596 stats->__f++; \
597 stats->tot_hitm++; \
598} while (0)
599
600#define PEER_INC(__f) \
601do { \
602 stats->__f++; \
603 stats->tot_peer++; \
604} while (0)
605
606#define P(a, b) PERF_MEM_##a##_##b
607
608 stats->nr_entries++;
609
610 if (lock & P(LOCK, LOCKED)) stats->locks++;
611
612 if (blk & P(BLK, DATA)) stats->blk_data++;
613 if (blk & P(BLK, ADDR)) stats->blk_addr++;
614
615 if (op & P(OP, LOAD)) {
616 /* load */
617 stats->load++;
618
619 if (!daddr) {
620 stats->ld_noadrs++;
621 return -1;
622 }
623
624 if (lvl & P(LVL, HIT)) {
625 if (lvl & P(LVL, UNC)) stats->ld_uncache++;
626 if (lvl & P(LVL, IO)) stats->ld_io++;
627 if (lvl & P(LVL, LFB)) stats->ld_fbhit++;
628 if (lvl & P(LVL, L1 )) stats->ld_l1hit++;
629 if (lvl & P(LVL, L2)) {
630 stats->ld_l2hit++;
631
632 if (snoopx & P(SNOOPX, PEER))
633 PEER_INC(lcl_peer);
634 }
635 if (lvl & P(LVL, L3 )) {
636 if (snoop & P(SNOOP, HITM))
637 HITM_INC(lcl_hitm);
638 else
639 stats->ld_llchit++;
640
641 if (snoopx & P(SNOOPX, PEER))
642 PEER_INC(lcl_peer);
643 }
644
645 if (lvl & P(LVL, LOC_RAM)) {
646 stats->lcl_dram++;
647 if (snoop & P(SNOOP, HIT))
648 stats->ld_shared++;
649 else
650 stats->ld_excl++;
651 }
652
653 if ((lvl & P(LVL, REM_RAM1)) ||
654 (lvl & P(LVL, REM_RAM2)) ||
655 mrem) {
656 stats->rmt_dram++;
657 if (snoop & P(SNOOP, HIT))
658 stats->ld_shared++;
659 else
660 stats->ld_excl++;
661 }
662 }
663
664 if ((lvl & P(LVL, REM_CCE1)) ||
665 (lvl & P(LVL, REM_CCE2)) ||
666 mrem) {
667 if (snoop & P(SNOOP, HIT)) {
668 stats->rmt_hit++;
669 } else if (snoop & P(SNOOP, HITM)) {
670 HITM_INC(rmt_hitm);
671 } else if (snoopx & P(SNOOPX, PEER)) {
672 stats->rmt_hit++;
673 PEER_INC(rmt_peer);
674 }
675 }
676
677 if ((lvl & P(LVL, MISS)))
678 stats->ld_miss++;
679
680 } else if (op & P(OP, STORE)) {
681 /* store */
682 stats->store++;
683
684 if (!daddr) {
685 stats->st_noadrs++;
686 return -1;
687 }
688
689 if (lvl & P(LVL, HIT)) {
690 if (lvl & P(LVL, UNC)) stats->st_uncache++;
691 if (lvl & P(LVL, L1 )) stats->st_l1hit++;
692 }
693 if (lvl & P(LVL, MISS))
694 if (lvl & P(LVL, L1)) stats->st_l1miss++;
695 if (lvl & P(LVL, NA))
696 stats->st_na++;
697 } else {
698 /* unparsable data_src? */
699 stats->noparse++;
700 return -1;
701 }
702
703 if (!mem_info__daddr(mi)->ms.map || !mem_info__iaddr(mi)->ms.map) {
704 stats->nomap++;
705 return -1;
706 }
707
708#undef P
709#undef HITM_INC
710 return err;
711}
712
713void c2c_add_stats(struct c2c_stats *stats, struct c2c_stats *add)
714{
715 stats->nr_entries += add->nr_entries;
716
717 stats->locks += add->locks;
718 stats->store += add->store;
719 stats->st_uncache += add->st_uncache;
720 stats->st_noadrs += add->st_noadrs;
721 stats->st_l1hit += add->st_l1hit;
722 stats->st_l1miss += add->st_l1miss;
723 stats->st_na += add->st_na;
724 stats->load += add->load;
725 stats->ld_excl += add->ld_excl;
726 stats->ld_shared += add->ld_shared;
727 stats->ld_uncache += add->ld_uncache;
728 stats->ld_io += add->ld_io;
729 stats->ld_miss += add->ld_miss;
730 stats->ld_noadrs += add->ld_noadrs;
731 stats->ld_fbhit += add->ld_fbhit;
732 stats->ld_l1hit += add->ld_l1hit;
733 stats->ld_l2hit += add->ld_l2hit;
734 stats->ld_llchit += add->ld_llchit;
735 stats->lcl_hitm += add->lcl_hitm;
736 stats->rmt_hitm += add->rmt_hitm;
737 stats->tot_hitm += add->tot_hitm;
738 stats->lcl_peer += add->lcl_peer;
739 stats->rmt_peer += add->rmt_peer;
740 stats->tot_peer += add->tot_peer;
741 stats->rmt_hit += add->rmt_hit;
742 stats->lcl_dram += add->lcl_dram;
743 stats->rmt_dram += add->rmt_dram;
744 stats->blk_data += add->blk_data;
745 stats->blk_addr += add->blk_addr;
746 stats->nomap += add->nomap;
747 stats->noparse += add->noparse;
748}