xref: /minix/minix/lib/libsys/llvm_gcov.c (revision 045e0ed3)
1 /* LLVM-to-GCOV converter by D.C. van Moolenbroek */
2 /*
3  * Originally, we had a GCOV code coverage implementation for GCC only.  We
4  * have now largely switched to LLVM, and LLVM uses a different internal
5  * implementation of the coverage data generation.  For regular userland
6  * programs, the implementation is part of LLVM compiler-rt's libprofile_rt.
7  * That implementation is unsuitable for our system services.  Instead, this
8  * file converts the calls used by LLVM into _gcov_f*() calls expected by our
9  * GCOV-for-GCC implementation, thus adding support for LLVM coverage by
10  * leveraging our previous GCC support.
11  */
12 
13 #if __clang__
14 
15 #include <stdlib.h>
16 #include <string.h>
17 #include <minix/syslib.h>
18 #include <minix/sysutil.h>
19 #include <minix/gcov.h>
20 #include <assert.h>
21 
22 /*
23  * What is the maximum number of source modules for one single system service?
24  * This number is currently way higher than needed, but if we ever add support
25  * for coverage of system service libraries (e.g., libsys and libminc), this
26  * number may not even be high enough.  A warning is printed on overflow.
27  * Note that we need this to be a static array, because we cannot use malloc()
28  * in particular in the initialization stage of the VM service.
29  */
30 #define NR_MODULES 256
31 
32 /*
33  * The code in this file is a MINIX3 service specific replacement of the
34  * GCDAProfiling.c code in LLVM's compiler-rt.  Their code cannot be used
35  * directly because they assume a userland environment, using all sorts of
36  * POSIX calls as well as malloc(3), none of which we can offer for system
37  * services in this case.  So, we provide our own implementation instead.
38  * However, while compiler-rt is always kept in sync with the LLVM profiling
39  * data emitter, we do not have that luxury.  The current version of this
40  * implementation has been written for LLVM 3.4 and 3.6, between which the LLVM
41  * GCOV ABI changed.  Our current implementation supports both versions, but
42  * may break with newer LLVM versions, even though we should be good up to and
43  * possibly including LLVM 4.0 at least.  Hopefully, at this point the LLVM
44  * GCOV ABI should have stabilized a bit.
45  *
46  * Note that since we do not have access to internal LLVM headers here, an ABI
47  * mismatch would not be noticable until llvm-cov fails to load the resulting
48  * files.  This whole mess is worth it only because we can really, really use
49  * the coverage information for our test sets..
50  */
51 #if __clang_major__ == 3 && __clang_minor__ == 4
52 #define LLVM_35	0	/* version 3.4 only */
53 #elif __clang_major__ == 3 && __clang_minor__ >= 5
54 #define LLVM_35	1	/* version 3.5 and later */
55 #else
56 #error "unknown LLVM/clang version, manual inspection required"
57 #endif
58 
59 typedef void (*write_cb_t)(void);
60 typedef void (*flush_cb_t)(void);
61 
62 /*
63  * Except for llvm_gcda_emit_function(), these functions are already declared
64  * in the 3.5+ ABI style.  With the 3.4 ABI, some parameters may have garbage.
65  */
66 void llvm_gcda_start_file(const char *, const char *, uint32_t);
67 void llvm_gcda_emit_function(uint32_t, const char *,
68 #if LLVM_35
69 	uint32_t, uint8_t, uint32_t);
70 #else
71 	uint8_t);
72 #endif
73 void llvm_gcda_emit_arcs(uint32_t, uint64_t *);
74 void llvm_gcda_summary_info(void);
75 void llvm_gcda_end_file(void);
76 void __gcov_flush(void);
77 void llvm_gcov_init(write_cb_t, flush_cb_t);
78 
79 static flush_cb_t flush_array[NR_MODULES];
80 static unsigned int flush_count = 0;
81 
82 static FILE *gcov_file = NULL;
83 
84 /*
85  * LLVM hook for opening the .gcda file for a specific source module.
86  */
87 void
88 llvm_gcda_start_file(const char * file_name, const char version[4],
89 	uint32_t stamp)
90 {
91 	uint32_t word[3];
92 
93 	assert(gcov_file == NULL);
94 
95 	gcov_file = _gcov_fopen(file_name, "w+b");
96 	assert(gcov_file != NULL);
97 
98 	/*
99 	 * Each _gcov_fwrite() invocation translates into a kernel call, so we
100 	 * want to aggregate writes as much as possible.
101 	 */
102 	word[0] = 0x67636461;				/* magic: "gcda" */
103 	memcpy(&word[1], version, sizeof(word[1]));	/* version */
104 #if LLVM_35
105 	word[2] = stamp;				/* stamp */
106 #else
107 	word[2] = 0x4C4C564D;				/* stamp: "LLVM" */
108 #endif
109 
110 	_gcov_fwrite(word, sizeof(word[0]), __arraycount(word), gcov_file);
111 }
112 
113 /*
114  * LLVM hook for writing a function announcement to the currently opened .gcda
115  * file.
116  */
117 void
118 llvm_gcda_emit_function(uint32_t ident, const char * func_name,
119 #if LLVM_35
120 	uint32_t func_cksum, uint8_t extra_cksum, uint32_t cfg_cksum)
121 #else
122 	uint8_t extra_cksum)
123 #endif
124 {
125 	uint32_t word[6];
126 	size_t words, len, wlen;
127 
128 	word[0] = 0x01000000;			/* tag: function */
129 	words = 2;
130 	word[2] = ident;			/* ident */
131 #if LLVM_35
132 	word[3] = func_cksum;			/* function checksum */
133 #else
134 	word[3] = 0;				/* function checksum */
135 #endif
136 	if (extra_cksum) {
137 #if LLVM_35
138 		word[4] = cfg_cksum;		/* configuration checksum */
139 #else
140 		word[4] = 0;			/* configuration checksum */
141 #endif
142 		words++;
143 	}
144 	word[1] = words;			/* length */
145 
146 	if (func_name != NULL) {
147 		len = strlen(func_name) + 1;
148 		wlen = len / sizeof(word[0]) + 1;
149 		word[1] += 1 + wlen;
150 		word[2 + words] = wlen;
151 		words++;
152 	}
153 
154 	_gcov_fwrite(word, sizeof(word[0]), 2 + words, gcov_file);
155 
156 	if (func_name != NULL) {
157 		_gcov_fwrite(func_name, 1, len, gcov_file);
158 		_gcov_fwrite("\0\0\0\0", 1, wlen * sizeof(uint32_t) - len,
159 		    gcov_file);
160 	}
161 }
162 
163 /*
164  * LLVM hook for writing function arc counters to the currently opened .gcda
165  * file.
166  */
167 void
168 llvm_gcda_emit_arcs(uint32_t ncounters, uint64_t * counters)
169 {
170 	uint32_t word[2];
171 
172 	assert(gcov_file != NULL);
173 
174 	word[0] = 0x01a10000;			/* tag: arc counters */
175 	word[1] = ncounters * 2;		/* length */
176 
177 	_gcov_fwrite(word, sizeof(word[0]), __arraycount(word), gcov_file);
178 	_gcov_fwrite(counters, sizeof(*counters), ncounters, gcov_file);
179 }
180 
181 /*
182  * LLVM hook for writing summary information to the currently opened .gcda
183  * file.
184  */
185 void
186 llvm_gcda_summary_info(void)
187 {
188 	uint32_t word[13];
189 
190 	memset(word, 0, sizeof(word));
191 	word[0] = 0xa1000000;			/* tag: object summary */
192 	word[1] = 9;				/* length */
193 	word[2] = 0;				/* checksum */
194 	word[3] = 0;				/* counter number */
195 	word[4] = 1;				/* runs */
196 	word[11] = 0xa3000000;			/* tag: program summary */
197 	word[12] = 0;				/* length */
198 
199 	_gcov_fwrite(word, sizeof(word[0]), __arraycount(word), gcov_file);
200 }
201 
202 /*
203  * LLVM hook for closing the currently opened .gcda file.
204  */
205 void
206 llvm_gcda_end_file(void)
207 {
208 	uint32_t word[2];
209 
210 	assert(gcov_file != NULL);
211 
212 	word[0] = 0;				/* tag: end of file */
213 	word[1] = 0;				/* length zero */
214 	_gcov_fwrite(word, sizeof(word[0]), __arraycount(word), gcov_file);
215 
216 	_gcov_fclose(gcov_file);
217 	gcov_file = NULL;
218 }
219 
220 /*
221  * Our implementation for LLVM of the GCC function to flush the coverage data.
222  * The function is called by our libsys's GCOV code.
223  */
224 void
225 __gcov_flush(void)
226 {
227 	unsigned int i;
228 
229 	/* Call the flush function for each registered module. */
230 	for (i = 0; i < flush_count; i++)
231 		flush_array[i]();
232 }
233 
234 /*
235  * LLVM hook for registration of write and flush callbacks.  The former is to
236  * be used on exit, the latter on a pre-exit flush.  We use the latter only.
237  * This function is basically called once for each compiled source module.
238  */
239 void
240 llvm_gcov_init(write_cb_t write_cb __unused, flush_cb_t flush_cb)
241 {
242 
243 	if (flush_cb == NULL)
244 		return;
245 
246 	/* If the array is full, drop this module. */
247 	if (flush_count == __arraycount(flush_array))
248 		return; /* array full, so we are going to miss information */
249 
250 	/* Add the flush function to the array. */
251 	flush_array[flush_count++] = flush_cb;
252 
253 	/*
254 	 * We print this warning here so that we print it only once.  What are
255 	 * the odds that there are *exactly* NR_MODULES modules anyway?
256 	 */
257 	if (flush_count == __arraycount(flush_array))
258 		printf("llvm_gcov: process %d has too many modules, "
259 		    "profiling data lost\n", sef_self());
260 }
261 
262 #endif /*__clang__*/
263