1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
/* Research 045: where Pure's machine instructions go on UAX #14, by mechanism. The app (aup's examples/linebreak.aup)
* on the lines 044's linebreak_check.py writes, its answers checked first; then ROUNDS passes with a user-mode
* instruction counter sampling its address every PERIOD instructions (perf_event_open, PERF_SAMPLE_IP, read from its
* ring after each line). Built without PIE, so where.py can give the addresses to addr2line as they are.
*
* cc -O2 -g -no-pie -std=c17 -I../../../core -I../../../../vega/text where.c ../../../../vega/text/linebreak.c \
* ../../../../vega/text/unicode-data.c ../../../build/libpure.a -lm -pthread -o where
* ./where MODULE RANGES LINES ROUNDS PERIOD > samples.txt "instructions N points N" then "ADDRESS COUNT" lines,
* the maps on stderr
* WHERE_VARIANT=1: a variant (events.c's), whose answers are not the test's. */
#define _GNU_SOURCE
#include "module.h"
#include "text.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <linux/perf_event.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
static unsigned char *whole(const char *path, size_t *size) {
FILE *f = fopen(path, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
*size = (size_t)ftell(f);
rewind(f);
unsigned char *bytes = malloc(*size ? *size : 1);
if (fread(bytes, 1, *size, f) != *size) { free(bytes); bytes = NULL; }
fclose(f);
return bytes;
}
typedef struct { uint32_t count; const uint32_t *codes; const unsigned char *want; unsigned char *payload; } Line;
/* A line's boundaries from Pure: the bytes view "boundaries", compared as breaks or not with want. */
typedef struct { const unsigned char *want; size_t at; bool same; } Compare;
static bool compare(void *context, PureSpan span) {
Compare *c = context;
for (size_t i = 0; i < span.size; ++i, ++c->at) c->same = c->same && (span.data[i] != 0) == c->want[c->at];
return true;
}
/* Addresses sampled, counted in an open table. */
enum { SLOTS = 1 << 20, PAGES = 64 };
static uint64_t address[SLOTS], hits[SLOTS];
static void count_address(uint64_t ip) {
for (uint64_t k = (ip * 0x9E3779B97F4A7C15u) >> 44;; k = (k + 1) & (SLOTS - 1)) {
if (address[k] == ip) { ++hits[k]; return; }
if (!address[k]) { address[k] = ip, hits[k] = 1; return; }
}
}
/* The samples the ring holds: a header and the address each (PERF_SAMPLE_IP); lost records skipped. */
static void drain(struct perf_event_mmap_page *head, size_t data_size) {
uint64_t end = __atomic_load_n(&head->data_head, __ATOMIC_ACQUIRE), at = head->data_tail;
const unsigned char *data = (const unsigned char *)head + head->data_offset;
while (at < end) {
struct perf_event_header h;
for (size_t i = 0; i < sizeof(h); ++i) ((unsigned char *)&h)[i] = data[(at + i) % data_size];
if (h.type == PERF_RECORD_SAMPLE) {
uint64_t ip;
for (size_t i = 0; i < sizeof(ip); ++i) ((unsigned char *)&ip)[i] = data[(at + sizeof(h) + i) % data_size];
count_address(ip);
}
at += h.size;
}
__atomic_store_n(&head->data_tail, at, __ATOMIC_RELEASE);
}
int main(int argc, char **argv) {
if (argc != 6) { fprintf(stderr, "usage: %s MODULE RANGES LINES ROUNDS PERIOD\n", argv[0]); return 2; }
size_t ranges_size, lines_size;
unsigned char *ranges = whole(argv[2], &ranges_size), *bytes = whole(argv[3], &lines_size);
int rounds = atoi(argv[4]);
uint64_t period = strtoull(argv[5], NULL, 10);
if (!ranges || !bytes) return 1;
static Line lines[1 << 16];
size_t count = 0, points = 0;
for (size_t at = 0; at + 4 <= lines_size && count < sizeof(lines) / sizeof(*lines); ++count) {
Line *l = &lines[count];
memcpy(&l->count, bytes + at, 4);
l->codes = (const uint32_t *)(bytes + at + 4);
l->want = bytes + at + 4 + 4 * (size_t)l->count;
at += 4 + 4 * (size_t)l->count + l->count + 1;
l->payload = malloc(8 * (size_t)l->count + 1);
for (uint32_t i = 0; i < l->count; ++i) {
double c = l->codes[i];
memcpy(l->payload + 8 * i, &c, 8);
}
points += l->count;
}
PureModule module = {0};
char error[160];
if (pure_module_read(argv[1], &module, error, sizeof(error)) != PURE_OK) { fprintf(stderr, "%s\n", error); return 1; }
PureApp app;
if (pure_app_start(&app, &module, (PureData){0}) != PURE_OK) return 1;
int load = pure_module_input(&module, "load"), line = pure_module_input(&module, "line");
if (pure_app_message(&app, (uint32_t)load, (PureSpan){ranges, ranges_size}) != PURE_OK) return 1;
static uint8_t got[4096];
size_t right = 0, pure_right = 0;
int boundaries = pure_module_view(&module, "boundaries");
for (size_t k = 0; k < count; ++k) { /* checked first: both give the test's boundaries, Pure's read from its view */
bool same = lines[k].count < sizeof(got) && vega_line_breaks(lines[k].codes, lines[k].count, got);
for (uint32_t i = 0; same && i <= lines[k].count; ++i) same = (got[i] != VEGA_BREAK_NONE) == lines[k].want[i];
right += same;
Compare c = {lines[k].want, 0, true};
c.same = boundaries >= 0 &&
pure_app_message(&app, (uint32_t)line, (PureSpan){lines[k].payload, 8 * (size_t)lines[k].count}) == PURE_OK &&
pure_app_view_text(&app, (uint32_t)boundaries, compare, &c) == PURE_OK;
pure_right += c.same && c.at == lines[k].count + 1;
}
if ((right != count || pure_right != count) && !getenv("WHERE_VARIANT")) {
fprintf(stderr, "right: C %zu, Pure %zu, of %zu\n", right, pure_right, count);
return 1;
}
struct perf_event_attr a;
memset(&a, 0, sizeof(a));
a.size = sizeof(a), a.type = PERF_TYPE_HARDWARE, a.config = PERF_COUNT_HW_INSTRUCTIONS, a.disabled = 1,
a.exclude_kernel = 1, a.exclude_hv = 1, a.sample_period = period, a.sample_type = PERF_SAMPLE_IP;
int fd = (int)syscall(SYS_perf_event_open, &a, 0, -1, -1, 0);
if (fd < 0) { perror("perf_event_open"); return 1; }
size_t page = (size_t)sysconf(_SC_PAGESIZE), data_size = PAGES * page;
struct perf_event_mmap_page *head = mmap(NULL, page + data_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (head == MAP_FAILED) { perror("mmap"); return 1; }
ioctl(fd, PERF_EVENT_IOC_RESET, 0);
for (int r = 0; r < rounds; ++r)
for (size_t k = 0; k < count; ++k) {
ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
PureResult did = pure_app_message(&app, (uint32_t)line, (PureSpan){lines[k].payload, 8 * (size_t)lines[k].count});
ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
if (did != PURE_OK) return 1;
drain(head, data_size);
}
uint64_t total = 0;
if (read(fd, &total, sizeof(total)) != sizeof(total)) return 1;
printf("instructions %llu points %llu\n", (unsigned long long)total, (unsigned long long)points * (unsigned long long)rounds);
for (size_t k = 0; k < SLOTS; ++k)
if (address[k]) printf("%llx %llu\n", (unsigned long long)address[k], (unsigned long long)hits[k]);
FILE *maps = fopen("/proc/self/maps", "r"); /* to stderr: where the shared libraries were (libc's malloc, memcpy) */
for (int c; maps && (c = fgetc(maps)) != EOF;) fputc(c, stderr);
if (maps) fclose(maps);
pure_app_stop(&app);
return 0;
}
|