Commit | Line | Data |
---|---|---|
618f726f | 1 | # Copyright (C) 2013-2016 Free Software Foundation, Inc. |
6eab34f3 DE |
2 | |
3 | # This program is free software; you can redistribute it and/or modify | |
4 | # it under the terms of the GNU General Public License as published by | |
5 | # the Free Software Foundation; either version 3 of the License, or | |
6 | # (at your option) any later version. | |
7 | # | |
8 | # This program is distributed in the hope that it will be useful, | |
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | # GNU General Public License for more details. | |
12 | # | |
13 | # You should have received a copy of the GNU General Public License | |
14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
15 | # | |
16 | # Notes: | |
17 | # 1) This follows a Python convention for marking internal vs public functions. | |
18 | # Internal functions are prefixed with "_". | |
19 | ||
20 | # A simple testcase generator. | |
21 | # | |
22 | # Usage Notes: | |
23 | # | |
24 | # 1) The length of each parameter list must either be one, in which case the | |
25 | # same value is used for each run, or the length must match all other | |
26 | # parameters of length greater than one. | |
27 | # | |
28 | # 2) Values for parameters that vary across runs must appear in increasing | |
29 | # order. E.g. nr_gen_shlibs = { 0 1 10 } is good, { 1 0 10 } is bad. | |
30 | # This rule simplifies the code a bit, without being onerous on the user: | |
31 | # a) Report generation doesn't have to sort the output by run, it'll already | |
32 | # be sorted. | |
33 | # b) In the static object file case, the last run can be used used to generate | |
34 | # all the source files. | |
35 | # | |
36 | # TODO: | |
37 | # 1) have functions call each other within an objfile and across | |
38 | # objfiles to measure things like backtrace times | |
39 | # 2) enums | |
40 | # | |
41 | # Implementation Notes: | |
42 | # | |
43 | # 1) The implementation would be a bit simpler if we could assume Tcl 8.5. | |
44 | # Then we could use a dictionary to record the testcase instead of an array. | |
45 | # With the array we use here, there is only one copy of it and instead of | |
46 | # passing its value we pass its name. Yay Tcl. An alternative is to just | |
47 | # use a global variable. | |
48 | # | |
49 | # 2) Because these programs can be rather large, we try to avoid recompilation | |
50 | # where we can. We don't have a makefile: we could generate one but it's | |
51 | # not clear that's simpler than our chosen mechanism which is to record | |
52 | # sums of all the inputs, and detect if an input has changed that way. | |
53 | ||
54 | if ![info exists CAT_PROGRAM] { | |
55 | set CAT_PROGRAM "/bin/cat" | |
56 | } | |
57 | ||
58 | # TODO(dje): Time md5sum vs sha1sum with our testcases. | |
59 | if ![info exists SHA1SUM_PROGRAM] { | |
60 | set SHA1SUM_PROGRAM "/usr/bin/sha1sum" | |
61 | } | |
62 | ||
63 | namespace eval GenPerfTest { | |
64 | ||
65 | # The default level of compilation parallelism we support. | |
66 | set DEFAULT_PERF_TEST_COMPILE_PARALLELISM 10 | |
67 | ||
68 | # The language of the test. | |
69 | set DEFAULT_LANGUAGE "c" | |
70 | ||
71 | # Extra source files for the binary. | |
72 | # This must at least include the file with main(), | |
73 | # and each test must supply its own. | |
74 | set DEFAULT_BINARY_EXTRA_SOURCES {} | |
75 | ||
76 | # Header files used by generated files and extra sources. | |
77 | set DEFAULT_BINARY_EXTRA_HEADERS {} | |
78 | ||
79 | # Extra source files for each generated shlib. | |
80 | # The compiler passes -DSHLIB=NNN which the source can use, for example, | |
81 | # to define unique symbols for each shlib. | |
82 | set DEFAULT_GEN_SHLIB_EXTRA_SOURCES {} | |
83 | ||
84 | # Header files used by generated files and extra sources. | |
85 | set DEFAULT_GEN_SHLIB_EXTRA_HEADERS {} | |
86 | ||
87 | # Source files for a tail shlib, or empty if none. | |
88 | # This library is loaded after all other shlibs (except any system shlibs | |
89 | # like libstdc++). It is useful for exercising issues that can appear | |
90 | # with system shlibs, without having to cope with implementation details | |
91 | # and bugs in system shlibs. E.g., gcc pr 65669. | |
92 | set DEFAULT_TAIL_SHLIB_SOURCES {} | |
93 | ||
94 | # Header files for the tail shlib. | |
95 | set DEFAULT_TAIL_SHLIB_HEADERS {} | |
96 | ||
97 | # The number of shared libraries to create. | |
98 | set DEFAULT_NR_GEN_SHLIBS 0 | |
99 | ||
100 | # The number of compunits in each objfile. | |
101 | set DEFAULT_NR_COMPUNITS 1 | |
102 | ||
103 | # The number of public globals in each compunit. | |
104 | set DEFAULT_NR_EXTERN_GLOBALS 1 | |
105 | ||
106 | # The number of static globals in each compunit. | |
107 | set DEFAULT_NR_STATIC_GLOBALS 1 | |
108 | ||
109 | # The number of public functions in each compunit. | |
110 | set DEFAULT_NR_EXTERN_FUNCTIONS 1 | |
111 | ||
112 | # The number of static functions in each compunit. | |
113 | set DEFAULT_NR_STATIC_FUNCTIONS 1 | |
114 | ||
115 | # Class generation. | |
116 | # This is only used if the selected language permits it. | |
117 | # The class specs here are used for each compunit. | |
118 | # Additional flexibility can be added as needed, but for now KISS. | |
119 | # | |
120 | # key/value list of: | |
121 | # count: number of classes | |
122 | # Default: 1 | |
123 | # name: list of namespaces and class name prefix | |
124 | # E.g., { ns0 ns1 foo } -> ns0::ns1::foo_<cu#>_{0,1,...} | |
125 | # There is no default, this value must be specified. | |
126 | # nr_members: number of members | |
127 | # Default: 0 | |
128 | # nr_static_members: number of static members | |
129 | # Default: 0 | |
130 | # nr_methods: number of methods | |
131 | # Default: 0 | |
132 | # nr_inline_methods: number of inline methods | |
133 | # Default: 0 | |
134 | # nr_static_methods: number of static methods | |
135 | # Default: 0 | |
136 | # nr_static_inline_methods: number of static inline methods | |
137 | # Default: 0 | |
138 | # | |
139 | # E.g., | |
140 | # class foo {}; | |
141 | # namespace ns1 { class foo {}; } | |
142 | # namespace ns2 { class bar {}; } | |
143 | # would be represented as | |
144 | # { | |
145 | # { count 1 name { foo } } | |
146 | # { count 1 name { ns1 foo } } | |
147 | # { count 1 name { ns2 bar } } | |
148 | # } | |
149 | # The actual generated class names will be | |
150 | # cu_N_foo_0, ns1::cu_N_foo_0, ns2::cu_N_bar_0 | |
151 | # where "N" is the CU number. | |
152 | # | |
153 | # To keep things simple for now, all class definitions go in headers, | |
154 | # one class per header, with non-inline method definitions going | |
155 | # into corresponding source files. | |
156 | set DEFAULT_CLASS_SPECS {} | |
157 | ||
158 | # The default value for the "count" field of class_specs. | |
159 | set DEFAULT_CLASS_COUNT 1 | |
160 | ||
161 | # The default number of members in each class. | |
162 | set DEFAULT_CLASS_NR_MEMBERS 0 | |
163 | ||
164 | # The default number of static members in each class. | |
165 | set DEFAULT_CLASS_NR_STATIC_MEMBERS 0 | |
166 | ||
167 | # The default number of methods in each class. | |
168 | set DEFAULT_CLASS_NR_METHODS 0 | |
169 | ||
170 | # The default number of inline methods in each class. | |
171 | set DEFAULT_CLASS_NR_INLINE_METHODS 0 | |
172 | ||
173 | # The default number of static methods in each class. | |
174 | set DEFAULT_CLASS_NR_STATIC_METHODS 0 | |
175 | ||
176 | # The default number of static inline methods in each class. | |
177 | set DEFAULT_CLASS_NR_STATIC_INLINE_METHODS 0 | |
178 | ||
179 | set header_suffixes(c) "h" | |
180 | set header_suffixes(c++) "h" | |
181 | set source_suffixes(c) "c" | |
182 | set source_suffixes(c++) "cc" | |
183 | ||
184 | # Generate .worker files that control building all the "pieces" of the | |
185 | # testcase. This doesn't include "main" or any test-specific stuff. | |
186 | # This mostly consists of the "bulk" (aka "crap" :-)) of the testcase to | |
187 | # give gdb something meaty to chew on. | |
188 | # The result is 0 for success, -1 for failure. | |
189 | # | |
190 | # Benchmarks generated by some of the tests are big. I mean really big. | |
191 | # And it's a pain to build one piece at a time, we need a parallel build. | |
192 | # To achieve this, given the framework we're working with, we need to | |
193 | # generate arguments to pass to a parallel make. This is done by | |
194 | # generating several files and then passing the file names to the parallel | |
195 | # make. All of the needed info is contained in the file name, so we could | |
196 | # do this differently, but this is pretty simple and flexible. | |
197 | ||
198 | proc gen_worker_files { test_description_exp } { | |
199 | global objdir PERF_TEST_COMPILE_PARALLELISM | |
200 | ||
201 | if { [file tail $test_description_exp] != $test_description_exp } { | |
202 | error "test description file contains directory name" | |
203 | } | |
204 | ||
205 | set program_name [file rootname $test_description_exp] | |
206 | set workers_dir "$objdir/gdb.perf/workers/$program_name" | |
207 | file mkdir $workers_dir | |
208 | ||
209 | set nr_workers $PERF_TEST_COMPILE_PARALLELISM | |
210 | verbose -log "gen_worker_files: $test_description_exp $nr_workers workers" | |
211 | ||
212 | for { set i 0 } { $i < $nr_workers } { incr i } { | |
213 | set file_name "${workers_dir}/${program_name}-${i}.worker" | |
214 | verbose -log "gen_worker_files: Generating $file_name" | |
215 | set f [open $file_name "w"] | |
216 | puts $f "# DO NOT EDIT, machine generated file." | |
217 | puts $f "# See perftest.exp:GenPerfTest::gen_worker_files." | |
218 | close $f | |
219 | } | |
220 | ||
221 | return 0 | |
222 | } | |
223 | ||
224 | # Load a perftest description. | |
225 | # Test descriptions are used to build the input files (binary + shlibs) | |
226 | # of one or more performance tests. | |
227 | ||
228 | proc load_test_description { basename } { | |
229 | global srcdir | |
230 | ||
231 | if { [file tail $basename] != $basename } { | |
232 | error "test description file contains directory name" | |
233 | } | |
234 | ||
235 | verbose -log "load_file $srcdir/gdb.perf/$basename" | |
236 | if { [load_file $srcdir/gdb.perf/$basename] == 0 } { | |
237 | error "Unable to load test description $basename" | |
238 | } | |
239 | } | |
240 | ||
241 | # Create a testcase object for test NAME. | |
242 | # The caller must call this as: | |
243 | # array set my_test [GenPerfTest::init_testcase $name] | |
244 | ||
245 | proc init_testcase { name } { | |
246 | set testcase(name) $name | |
247 | set testcase(language) $GenPerfTest::DEFAULT_LANGUAGE | |
248 | set testcase(run_names) [list $name] | |
249 | set testcase(binary_extra_sources) $GenPerfTest::DEFAULT_BINARY_EXTRA_SOURCES | |
250 | set testcase(binary_extra_headers) $GenPerfTest::DEFAULT_BINARY_EXTRA_HEADERS | |
251 | set testcase(gen_shlib_extra_sources) $GenPerfTest::DEFAULT_GEN_SHLIB_EXTRA_SOURCES | |
252 | set testcase(gen_shlib_extra_headers) $GenPerfTest::DEFAULT_GEN_SHLIB_EXTRA_HEADERS | |
253 | set testcase(tail_shlib_sources) $GenPerfTest::DEFAULT_TAIL_SHLIB_SOURCES | |
254 | set testcase(tail_shlib_headers) $GenPerfTest::DEFAULT_TAIL_SHLIB_HEADERS | |
255 | set testcase(nr_gen_shlibs) $GenPerfTest::DEFAULT_NR_GEN_SHLIBS | |
256 | set testcase(nr_compunits) $GenPerfTest::DEFAULT_NR_COMPUNITS | |
257 | ||
258 | set testcase(nr_extern_globals) $GenPerfTest::DEFAULT_NR_EXTERN_GLOBALS | |
259 | set testcase(nr_static_globals) $GenPerfTest::DEFAULT_NR_STATIC_GLOBALS | |
260 | set testcase(nr_extern_functions) $GenPerfTest::DEFAULT_NR_EXTERN_FUNCTIONS | |
261 | set testcase(nr_static_functions) $GenPerfTest::DEFAULT_NR_STATIC_FUNCTIONS | |
262 | ||
263 | set testcase(class_specs) $GenPerfTest::DEFAULT_CLASS_SPECS | |
264 | ||
265 | # The location of this file drives the location of all other files. | |
266 | # The choice is derived from standard_output_file. We don't use it | |
267 | # because of the parallel build support, we want each worker's log/sum | |
268 | # files to go in different directories, but we don't want their output | |
269 | # to go in different directories. | |
270 | # N.B. The value here must be kept in sync with Makefile.in. | |
271 | global objdir | |
272 | set name_no_spaces [_convert_spaces $name] | |
273 | set testcase(binfile) "$objdir/gdb.perf/outputs/$name_no_spaces/$name_no_spaces" | |
274 | ||
275 | return [array get testcase] | |
276 | } | |
277 | ||
278 | proc _verify_parameter_lengths { self_var } { | |
279 | upvar 1 $self_var self | |
280 | set params { | |
281 | binary_extra_sources binary_extra_headers | |
282 | gen_shlib_extra_sources gen_shlib_extra_headers | |
283 | tail_shlib_sources tail_shlib_headers | |
284 | nr_gen_shlibs nr_compunits | |
285 | nr_extern_globals nr_static_globals | |
286 | nr_extern_functions nr_static_functions | |
287 | class_specs | |
288 | } | |
289 | set nr_runs [llength $self(run_names)] | |
290 | foreach p $params { | |
291 | set n [llength $self($p)] | |
292 | if { $n > 1 } { | |
293 | if { $n != $nr_runs } { | |
294 | error "Bad number of values for parameter $p" | |
295 | } | |
296 | set values $self($p) | |
297 | for { set i 0 } { $i < $n - 1 } { incr i } { | |
298 | if { [lindex $values $i] > [lindex $values [expr $i + 1]] } { | |
299 | error "Values of parameter $p are not increasing" | |
300 | } | |
301 | } | |
302 | } | |
303 | } | |
304 | } | |
305 | ||
306 | # Verify the class_specs parameter. | |
307 | ||
308 | proc _verify_class_specs { self_var } { | |
309 | upvar 1 $self_var self | |
310 | set nr_runs [llength $self(run_names)] | |
311 | for { set run_nr 0 } { $run_nr < $nr_runs } { incr run_nr } { | |
312 | set class_specs [_get_param $self(class_specs) $run_nr] | |
313 | foreach { spec } $class_specs { | |
314 | if { [llength $spec] % 2 != 0 } { | |
315 | error "Uneven number of values in class spec: $spec" | |
316 | } | |
317 | foreach { key value } $spec { | |
318 | switch -exact -- $key { | |
319 | name { } | |
320 | count - | |
321 | nr_members - nr_static_members - | |
322 | nr_methods - nr_static_methods - | |
323 | nr_inline_methods - nr_static_inline_methods | |
324 | { | |
325 | if ![string is integer $value] { | |
326 | error "Non-integer value $value for key $key in class_specs: $class_specs" | |
327 | } | |
328 | } | |
329 | default { | |
330 | error "Unrecognized key $key in class_specs: $class_specs" | |
331 | } | |
332 | } | |
333 | } | |
334 | } | |
335 | } | |
336 | } | |
337 | ||
338 | # Verify the testcase is valid (as best we can, this isn't exhaustive). | |
339 | ||
340 | proc _verify_testcase { self_var } { | |
341 | upvar 1 $self_var self | |
342 | _verify_parameter_lengths self | |
343 | _verify_class_specs self | |
344 | ||
345 | # Each test must supply its own main(). We don't check for main here, | |
346 | # but we do verify the test supplied something. | |
347 | if { [llength $self(binary_extra_sources)] == 0 } { | |
348 | error "Missing value for binary_extra_sources" | |
349 | } | |
350 | } | |
351 | ||
352 | # Return the value of parameter PARAM for run RUN_NR. | |
353 | ||
354 | proc _get_param { param run_nr } { | |
355 | if { [llength $param] == 1 } { | |
356 | # Since PARAM may be a list of lists we need to use lindex. This | |
357 | # also works for scalars (scalars are degenerate lists). | |
358 | return [lindex $param 0] | |
359 | } | |
360 | return [lindex $param $run_nr] | |
361 | } | |
362 | ||
363 | # Return non-zero if all files (binaries + shlibs) can be compiled from | |
364 | # one set of object files. This is a simple optimization to speed up | |
365 | # test build times. This happens if the only variation among runs is | |
366 | # nr_gen_shlibs or nr_compunits. | |
367 | ||
368 | proc _static_object_files_p { self_var } { | |
369 | upvar 1 $self_var self | |
370 | # These values are either scalars, or can vary across runs but don't | |
371 | # affect whether we can share the generated object objects between | |
372 | # runs. | |
373 | set static_object_file_params { | |
374 | name language run_names nr_gen_shlibs nr_compunits | |
375 | binary_extra_sources gen_shlib_extra_sources tail_shlib_sources | |
376 | } | |
377 | foreach name [array names self] { | |
378 | if { [lsearch $static_object_file_params $name] < 0 } { | |
379 | # name is not in static_object_file_params. | |
380 | if { [llength $self($name)] > 1 } { | |
381 | # The user could provide a list that is all the same value, | |
382 | # so check for that. | |
383 | set first_value [lindex $self($name) 0] | |
384 | foreach elm [lrange $self($name) 1 end] { | |
385 | if { $elm != $first_value } { | |
386 | return 0 | |
387 | } | |
388 | } | |
389 | } | |
390 | } | |
391 | } | |
392 | return 1 | |
393 | } | |
394 | ||
395 | # Return non-zero if classes are enabled. | |
396 | ||
397 | proc _classes_enabled_p { self_var run_nr } { | |
398 | upvar 1 $self_var self | |
399 | set class_specs [_get_param $self(class_specs) $run_nr] | |
400 | return [expr [llength $class_specs] > 0] | |
401 | } | |
402 | ||
403 | # Spaces in file names are a pain, remove them. | |
404 | # They appear if the user puts spaces in the test name or run name. | |
405 | ||
406 | proc _convert_spaces { file_name } { | |
407 | return [regsub -all " " $file_name "-"] | |
408 | } | |
409 | ||
410 | # Return the compilation flags for the test. | |
411 | ||
412 | proc _compile_options { self_var } { | |
413 | upvar 1 $self_var self | |
414 | set result {debug} | |
415 | switch $self(language) { | |
416 | c++ { | |
417 | lappend result "c++" | |
418 | } | |
419 | } | |
420 | return $result | |
421 | } | |
422 | ||
423 | # Return the path to put source/object files in for run number RUN_NR. | |
424 | ||
425 | proc _make_object_dir_name { self_var static run_nr } { | |
426 | upvar 1 $self_var self | |
427 | # Note: The output directory already includes the name of the test | |
428 | # description file. | |
429 | set bindir [file dirname $self(binfile)] | |
430 | # Put the pieces in a subdirectory, there are a lot of them. | |
431 | if $static { | |
432 | return "$bindir/pieces" | |
433 | } else { | |
434 | set run_name [_convert_spaces [lindex $self(run_names) $run_nr]] | |
435 | return "$bindir/pieces/$run_name" | |
436 | } | |
437 | } | |
438 | ||
439 | # RUN_NR is ignored if STATIC is non-zero. | |
440 | # SO_NR is the shlib number or "" for the binary. | |
441 | # CU_NR is either the compilation unit number or "main". | |
442 | ||
443 | proc _make_header_basename { self_var static run_nr so_nr cu_nr } { | |
444 | upvar 1 $self_var self | |
445 | set header_suffix $GenPerfTest::header_suffixes($self(language)) | |
446 | if { !$static } { | |
447 | set run_name [_get_param $self(run_names) $run_nr] | |
448 | if { "$so_nr" != "" } { | |
449 | set header_name "${run_name}-lib${so_nr}-cu${cu_nr}.$header_suffix" | |
450 | } else { | |
451 | set header_name "${run_name}-cu${cu_nr}.$header_suffix" | |
452 | } | |
453 | } else { | |
454 | if { "$so_nr" != "" } { | |
455 | set header_name "lib${so_nr}-cu${cu_nr}.$header_suffix" | |
456 | } else { | |
457 | set header_name "cu${cu_nr}.$header_suffix" | |
458 | } | |
459 | } | |
460 | return "[_convert_spaces $header_name]" | |
461 | } | |
462 | ||
463 | # RUN_NR is ignored if STATIC is non-zero. | |
464 | # SO_NR is the shlib number or "" for the binary. | |
465 | # CU_NR is either the compilation unit number or "main". | |
466 | ||
467 | proc _make_header_name { self_var static run_nr so_nr cu_nr } { | |
468 | upvar 1 $self_var self | |
469 | set header_name [_make_header_basename self $static $run_nr $so_nr $cu_nr] | |
470 | return "[_make_object_dir_name self $static $run_nr]/$header_name" | |
471 | } | |
472 | ||
473 | # RUN_NR is ignored if STATIC is non-zero. | |
474 | # SO_NR is the shlib number or "" for the binary. | |
475 | # CU_NR is either the compilation unit number or "main". | |
476 | ||
477 | proc _make_source_basename { self_var static run_nr so_nr cu_nr } { | |
478 | upvar 1 $self_var self | |
479 | set source_suffix $GenPerfTest::source_suffixes($self(language)) | |
480 | if { !$static } { | |
481 | set run_name [_get_param $self(run_names) $run_nr] | |
482 | if { "$so_nr" != "" } { | |
483 | set source_name "${run_name}-lib${so_nr}-cu${cu_nr}.$source_suffix" | |
484 | } else { | |
485 | set source_name "${run_name}-cu${cu_nr}.$source_suffix" | |
486 | } | |
487 | } else { | |
488 | if { "$so_nr" != "" } { | |
489 | set source_name "lib${so_nr}-cu${cu_nr}.$source_suffix" | |
490 | } else { | |
491 | set source_name "cu${cu_nr}.$source_suffix" | |
492 | } | |
493 | } | |
494 | return "[_convert_spaces $source_name]" | |
495 | } | |
496 | ||
497 | # RUN_NR is ignored if STATIC is non-zero. | |
498 | # SO_NR is the shlib number or "" for the binary. | |
499 | # CU_NR is either the compilation unit number or "main". | |
500 | ||
501 | proc _make_source_name { self_var static run_nr so_nr cu_nr } { | |
502 | upvar 1 $self_var self | |
503 | set source_name [_make_source_basename self $static $run_nr $so_nr $cu_nr] | |
504 | return "[_make_object_dir_name self $static $run_nr]/$source_name" | |
505 | } | |
506 | ||
507 | # Generated object files get put in the same directory as their source. | |
508 | # WARNING: This means that we can't do parallel compiles from the same | |
509 | # source file, they have to have different names. | |
510 | ||
511 | proc _make_binary_object_name { self_var static run_nr cu_nr } { | |
512 | upvar 1 $self_var self | |
513 | set source_name [_make_source_name self $static $run_nr "" $cu_nr] | |
514 | return [file rootname $source_name].o | |
515 | } | |
516 | ||
517 | # Return the list of source/object files for the binary. | |
518 | # This is the source files specified in test param binary_extra_sources as | |
519 | # well as the names of all the object file "pieces". | |
520 | # STATIC is the value of _static_object_files_p for the test. | |
521 | ||
522 | proc _make_binary_input_file_names { self_var static run_nr } { | |
523 | upvar 1 $self_var self | |
524 | global srcdir subdir | |
525 | set nr_compunits [_get_param $self(nr_compunits) $run_nr] | |
526 | set result {} | |
527 | foreach source [_get_param $self(binary_extra_sources) $run_nr] { | |
528 | lappend result "$srcdir/$subdir/$source" | |
529 | } | |
530 | for { set cu_nr 0 } { $cu_nr < $nr_compunits } { incr cu_nr } { | |
531 | lappend result [_make_binary_object_name self $static $run_nr $cu_nr] | |
532 | } | |
533 | return $result | |
534 | } | |
535 | ||
536 | proc _make_binary_name { self_var run_nr } { | |
537 | upvar 1 $self_var self | |
538 | set run_name [_get_param $self(run_names) $run_nr] | |
539 | set exe_name "$self(binfile)-[_convert_spaces ${run_name}]" | |
540 | return $exe_name | |
541 | } | |
542 | ||
543 | # SO_NAME is either a shlib number or "tail". | |
544 | ||
545 | proc _make_shlib_name { self_var static run_nr so_name } { | |
546 | upvar 1 $self_var self | |
547 | if { !$static } { | |
548 | set run_name [_get_param $self(run_names) $run_nr] | |
549 | set lib_name "$self(name)-${run_name}-lib${so_name}.so" | |
550 | } else { | |
551 | set lib_name "$self(name)-lib${so_name}.so" | |
552 | } | |
553 | set output_dir [file dirname $self(binfile)] | |
554 | return "[_make_object_dir_name self $static $run_nr]/[_convert_spaces $lib_name]" | |
555 | } | |
556 | ||
557 | proc _create_file { self_var path } { | |
558 | upvar 1 $self_var self | |
559 | verbose -log "Creating file: $path" | |
560 | set f [open $path "w"] | |
561 | return $f | |
562 | } | |
563 | ||
564 | proc _write_intro { self_var f } { | |
565 | upvar 1 $self_var self | |
566 | puts $f "// DO NOT EDIT, machine generated file." | |
567 | puts $f "// See perftest.exp:GenPerfTest." | |
568 | } | |
569 | ||
570 | proc _write_includes { self_var f includes } { | |
571 | upvar 1 $self_var self | |
572 | if { [llength $includes] > 0 } { | |
573 | puts $f "" | |
574 | } | |
575 | foreach i $includes { | |
576 | switch -glob -- $i { | |
577 | "<*>" { | |
578 | puts $f "#include $i" | |
579 | } | |
580 | default { | |
581 | puts $f "#include \"$i\"" | |
582 | } | |
583 | } | |
584 | } | |
585 | } | |
586 | ||
587 | proc _make_header_macro { name c } { | |
588 | return [string toupper "${name}_${c}"] | |
589 | } | |
590 | ||
591 | proc _write_static_globals { self_var f run_nr } { | |
592 | upvar 1 $self_var self | |
593 | puts $f "" | |
594 | set nr_static_globals [_get_param $self(nr_static_globals) $run_nr] | |
595 | # Rather than parameterize the number of const/non-const globals, | |
596 | # and their types, we keep it simple for now. Even the number of | |
597 | # bss/non-bss globals may be useful; later, if warranted. | |
598 | for { set i 0 } { $i < $nr_static_globals } { incr i } { | |
599 | if { $i % 2 == 0 } { | |
600 | set const "const " | |
601 | } else { | |
602 | set const "" | |
603 | } | |
604 | puts $f "static ${const}int static_global_$i = $i;" | |
605 | } | |
606 | } | |
607 | ||
608 | # ID is "" for the binary, and a unique symbol prefix for each SO. | |
609 | ||
610 | proc _write_extern_globals { self_var f run_nr id cu_nr } { | |
611 | upvar 1 $self_var self | |
612 | puts $f "" | |
613 | set nr_extern_globals [_get_param $self(nr_extern_globals) $run_nr] | |
614 | # Rather than parameterize the number of const/non-const globals, | |
615 | # and their types, we keep it simple for now. Even the number of | |
616 | # bss/non-bss globals may be useful; later, if warranted. | |
617 | for { set i 0 } { $i < $nr_extern_globals } { incr i } { | |
618 | if { $i % 2 == 0 } { | |
619 | set const "const " | |
620 | } else { | |
621 | set const "" | |
622 | } | |
623 | puts $f "${const}int ${id}global_${cu_nr}_$i = $cu_nr * 1000 + $i;" | |
624 | } | |
625 | } | |
626 | ||
627 | proc _write_static_functions { self_var f run_nr } { | |
628 | upvar 1 $self_var self | |
629 | set nr_static_functions [_get_param $self(nr_static_functions) $run_nr] | |
630 | for { set i 0 } { $i < $nr_static_functions } { incr i } { | |
631 | puts $f "" | |
632 | puts $f "static void" | |
633 | puts $f "static_function_$i (void)" | |
634 | puts $f "{" | |
635 | puts $f "}" | |
636 | } | |
637 | } | |
638 | ||
639 | # ID is "" for the binary, and a unique symbol prefix for each SO. | |
640 | ||
641 | proc _write_extern_functions { self_var f run_nr id cu_nr } { | |
642 | upvar 1 $self_var self | |
643 | set nr_extern_functions [_get_param $self(nr_extern_functions) $run_nr] | |
644 | for { set i 0 } { $i < $nr_extern_functions } { incr i } { | |
645 | puts $f "" | |
646 | puts $f "void" | |
647 | puts $f "${id}function_${cu_nr}_$i (void)" | |
648 | puts $f "{" | |
649 | puts $f "}" | |
650 | } | |
651 | } | |
652 | ||
653 | proc _get_class_spec { spec name } { | |
654 | foreach { key value } $spec { | |
655 | if { $key == $name } { | |
656 | return $value | |
657 | } | |
658 | } | |
659 | switch $name { | |
660 | count { | |
661 | return $GenPerfTest::DEFAULT_CLASS_COUNT | |
662 | } | |
663 | nr_members { | |
664 | return $GenPerfTest::DEFAULT_CLASS_NR_MEMBERS | |
665 | } | |
666 | nr_static_members { | |
667 | return $GenPerfTest::DEFAULT_CLASS_NR_STATIC_MEMBERS | |
668 | } | |
669 | nr_methods { | |
670 | return $GenPerfTest::DEFAULT_CLASS_NR_METHODS | |
671 | } | |
672 | nr_inline_methods { | |
673 | return $GenPerfTest::DEFAULT_CLASS_NR_INLINE_METHODS | |
674 | } | |
675 | nr_static_methods { | |
676 | return $GenPerfTest::DEFAULT_CLASS_NR_STATIC_METHODS | |
677 | } | |
678 | nr_static_inline_methods { | |
679 | return $GenPerfTest::DEFAULT_CLASS_NR_STATIC_INLINE_METHODS | |
680 | } | |
681 | default { | |
682 | error "required class-spec not present: $name" | |
683 | } | |
684 | } | |
685 | } | |
686 | ||
687 | # SO_NR is the shlib number or "" for the binary. | |
688 | # CU_NR is either the compilation unit number or "main". | |
689 | # NAME is the "name" field from the class spec, which is | |
690 | # { ns0 ns1 ... nsN class_name }. | |
691 | # C is the iteration number, from the "count" field from the class spec. | |
692 | ||
693 | proc _make_class_name { so_nr cu_nr name c } { | |
694 | set class_name [lindex $name [expr [llength $name] - 1]] | |
695 | if { "$so_nr" != "" } { | |
696 | set prefix "shlib${so_nr}_" | |
697 | } else { | |
698 | set prefix "" | |
699 | } | |
700 | return "${prefix}cu_${cu_nr}_${class_name}_${c}" | |
701 | } | |
702 | ||
703 | proc _make_namespace_name { name } { | |
704 | if { "$name" == "anonymous" } { | |
705 | return "" | |
706 | } | |
707 | return $name | |
708 | } | |
709 | ||
710 | proc _write_class_definitions { self_var f static run_nr so_nr cu_nr } { | |
711 | upvar 1 $self_var self | |
712 | set class_specs [_get_param $self(class_specs) $run_nr] | |
713 | foreach spec $class_specs { | |
714 | set count [_get_class_spec $spec count] | |
715 | set name [_get_class_spec $spec name] | |
716 | set nr_members [_get_class_spec $spec nr_members] | |
717 | set nr_static_members [_get_class_spec $spec nr_static_members] | |
718 | set nr_methods [_get_class_spec $spec nr_methods] | |
719 | set nr_static_methods [_get_class_spec $spec nr_static_methods] | |
720 | set depth [expr [llength $name] - 1] | |
721 | for { set c 0 } { $c < $count } { incr c } { | |
722 | puts $f "" | |
723 | for { set i 0 } { $i < $depth } { incr i } { | |
724 | puts $f "namespace [_make_namespace_name [lindex $name $i]]" | |
725 | puts $f "\{" | |
726 | puts $f "" | |
727 | } | |
728 | set class_name [_make_class_name $so_nr $cu_nr $name $c] | |
729 | puts $f "class $class_name" | |
730 | puts $f "\{" | |
731 | puts $f " public:" | |
732 | for { set i 0 } { $i < $nr_members } { incr i } { | |
733 | puts $f " int member_$i;" | |
734 | } | |
735 | for { set i 0 } { $i < $nr_static_members } { incr i } { | |
736 | # Rather than parameterize the number of const/non-const | |
737 | # members, and their types, we keep it simple for now. | |
738 | if { $i % 2 == 0 } { | |
739 | puts $f " static const int static_member_$i = $i;" | |
740 | } else { | |
741 | puts $f " static int static_member_$i;" | |
742 | } | |
743 | } | |
744 | for { set i 0 } { $i < $nr_methods } { incr i } { | |
745 | puts $f " void method_$i (void);" | |
746 | } | |
747 | for { set i 0 } { $i < $nr_static_methods } { incr i } { | |
748 | puts $f " static void static_method_$i (void);" | |
749 | } | |
750 | _write_inline_methods self $f $so_nr $cu_nr $spec $c | |
751 | _write_static_inline_methods self $f $so_nr $cu_nr $spec $c | |
752 | puts $f "\};" | |
753 | for { set i [expr $depth - 1] } { $i >= 0 } { incr i -1 } { | |
754 | puts $f "" | |
755 | puts $f "\} // [lindex $name $i]" | |
756 | } | |
757 | } | |
758 | } | |
759 | } | |
760 | ||
761 | proc _write_inline_methods { self_var f so_nr cu_nr spec c } { | |
762 | upvar 1 $self_var self | |
763 | set name [_get_class_spec $spec name] | |
764 | set nr_inline_methods [_get_class_spec $spec nr_inline_methods] | |
765 | for { set i 0 } { $i < $nr_inline_methods } { incr i } { | |
766 | puts $f " void inline_method_$i (void) { }" | |
767 | } | |
768 | } | |
769 | ||
770 | proc _write_static_inline_methods { self_var f so_nr cu_nr spec c } { | |
771 | upvar 1 $self_var self | |
772 | set name [_get_class_spec $spec name] | |
773 | set nr_static_inline_methods [_get_class_spec $spec nr_static_inline_methods] | |
774 | for { set i 0 } { $i < $nr_static_inline_methods } { incr i } { | |
775 | puts $f " static void static_inline_method_$i (void) { }" | |
776 | } | |
777 | } | |
778 | ||
779 | proc _write_class_implementations { self_var f static run_nr so_nr cu_nr } { | |
780 | upvar 1 $self_var self | |
781 | set class_specs [_get_param $self(class_specs) $run_nr] | |
782 | foreach spec $class_specs { | |
783 | set count [_get_class_spec $spec count] | |
784 | set name [_get_class_spec $spec name] | |
785 | set depth [expr [llength $name] - 1] | |
786 | for { set c 0 } { $c < $count } { incr c } { | |
787 | for { set i 0 } { $i < $depth } { incr i } { | |
788 | puts $f "" | |
789 | puts $f "namespace [_make_namespace_name [lindex $name $i]]" | |
790 | puts $f "\{" | |
791 | } | |
792 | _write_static_members self $f $so_nr $cu_nr $spec $c | |
793 | _write_methods self $f $so_nr $cu_nr $spec $c | |
794 | _write_static_methods self $f $so_nr $cu_nr $spec $c | |
795 | for { set i [expr $depth - 1] } { $i >= 0 } { incr i -1 } { | |
796 | puts $f "" | |
797 | puts $f "\} // [lindex $name $i]" | |
798 | } | |
799 | } | |
800 | } | |
801 | } | |
802 | ||
803 | proc _write_static_members { self_var f so_nr cu_nr spec c } { | |
804 | upvar 1 $self_var self | |
805 | set name [_get_class_spec $spec name] | |
806 | set nr_static_members [_get_class_spec $spec nr_static_members] | |
807 | set class_name [_make_class_name $so_nr $cu_nr $name $c] | |
808 | puts $f "" | |
809 | # Rather than parameterize the number of const/non-const | |
810 | # members, and their types, we keep it simple for now. | |
811 | for { set i 0 } { $i < $nr_static_members } { incr i } { | |
812 | if { $i % 2 == 0 } { | |
813 | # Static const members are initialized inline. | |
814 | } else { | |
815 | puts $f "int ${class_name}::static_member_$i = $i;" | |
816 | } | |
817 | } | |
818 | } | |
819 | ||
820 | proc _write_methods { self_var f so_nr cu_nr spec c } { | |
821 | upvar 1 $self_var self | |
822 | set name [_get_class_spec $spec name] | |
823 | set nr_methods [_get_class_spec $spec nr_methods] | |
824 | set class_name [_make_class_name $so_nr $cu_nr $name $c] | |
825 | for { set i 0 } { $i < $nr_methods } { incr i } { | |
826 | puts $f "" | |
827 | puts $f "void" | |
828 | puts $f "${class_name}::method_$i (void)" | |
829 | puts $f "{" | |
830 | puts $f "}" | |
831 | } | |
832 | } | |
833 | ||
834 | proc _write_static_methods { self_var f so_nr cu_nr spec c } { | |
835 | upvar 1 $self_var self | |
836 | set name [_get_class_spec $spec name] | |
837 | set nr_static_methods [_get_class_spec $spec nr_static_methods] | |
838 | set class_name [_make_class_name $so_nr $cu_nr $name $c] | |
839 | for { set i 0 } { $i < $nr_static_methods } { incr i } { | |
840 | puts $f "" | |
841 | puts $f "void" | |
842 | puts $f "${class_name}::static_method_$i (void)" | |
843 | puts $f "{" | |
844 | puts $f "}" | |
845 | } | |
846 | } | |
847 | ||
848 | proc _gen_compunit_header { self_var static run_nr so_nr cu_nr } { | |
849 | upvar 1 $self_var self | |
850 | set header_file [_make_header_name self $static $run_nr $so_nr $cu_nr] | |
851 | set f [_create_file self $header_file] | |
852 | _write_intro self $f | |
853 | set header_macro [_make_header_macro "HEADER_CU" $cu_nr] | |
854 | puts $f "" | |
855 | puts $f "#ifndef $header_macro" | |
856 | puts $f "#define $header_macro" | |
857 | if [_classes_enabled_p self $run_nr] { | |
858 | _write_class_definitions self $f $static $run_nr $so_nr $cu_nr | |
859 | } | |
860 | puts $f "" | |
861 | puts $f "#endif // $header_macro" | |
862 | close $f | |
863 | return $header_file | |
864 | } | |
865 | ||
866 | proc _gen_binary_compunit_source { self_var static run_nr cu_nr } { | |
867 | upvar 1 $self_var self | |
868 | set source_file [_make_source_name self $static $run_nr "" $cu_nr] | |
869 | set f [_create_file self $source_file] | |
870 | _write_intro self $f | |
871 | _write_includes self $f [_get_param $self(binary_extra_headers) $run_nr] | |
872 | set header_file [_make_header_basename self $static $run_nr "" $cu_nr] | |
873 | puts $f "#include \"$header_file\"" | |
874 | _write_static_globals self $f $run_nr | |
875 | _write_extern_globals self $f $run_nr "" $cu_nr | |
876 | _write_static_functions self $f $run_nr | |
877 | _write_extern_functions self $f $run_nr "" $cu_nr | |
878 | if [_classes_enabled_p self $run_nr] { | |
879 | _write_class_implementations self $f $static $run_nr "" $cu_nr | |
880 | } | |
881 | close $f | |
882 | return $source_file | |
883 | } | |
884 | ||
885 | # Generate the sources for the pieces of the binary. | |
886 | # The result is a list of source file names and accompanying object file | |
887 | # names. The pieces are split across workers. | |
888 | # E.g., with 10 workers the result for worker 0 is | |
889 | # { { source0 header0 object0 } { source10 header10 object10 } ... } | |
890 | ||
891 | proc _gen_binary_source { self_var worker_nr static run_nr } { | |
892 | upvar 1 $self_var self | |
893 | verbose -log "GenPerfTest::_gen_binary_source worker $worker_nr run $run_nr, started [timestamp -format %c]" | |
894 | set nr_compunits [_get_param $self(nr_compunits) $run_nr] | |
895 | global PERF_TEST_COMPILE_PARALLELISM | |
896 | set nr_workers $PERF_TEST_COMPILE_PARALLELISM | |
897 | set result {} | |
898 | for { set cu_nr $worker_nr } { $cu_nr < $nr_compunits } { incr cu_nr $nr_workers } { | |
899 | set header_file [_gen_compunit_header self $static $run_nr "" $cu_nr] | |
900 | set source_file [_gen_binary_compunit_source self $static $run_nr $cu_nr] | |
901 | set object_file [_make_binary_object_name self $static $run_nr $cu_nr] | |
902 | lappend result [list $source_file $header_file $object_file] | |
903 | } | |
904 | verbose -log "GenPerfTest::_gen_binary_source worker $worker_nr run $run_nr, done [timestamp -format %c]" | |
905 | return $result | |
906 | } | |
907 | ||
908 | proc _gen_shlib_compunit_source { self_var static run_nr so_nr cu_nr } { | |
909 | upvar 1 $self_var self | |
910 | set source_file [_make_source_name self $static $run_nr $so_nr $cu_nr] | |
911 | set f [_create_file self $source_file] | |
912 | _write_intro self $f | |
913 | _write_includes self $f [_get_param $self(gen_shlib_extra_headers) $run_nr] | |
914 | set header_file [_make_header_basename self $static $run_nr $so_nr $cu_nr] | |
915 | puts $f "#include \"$header_file\"" | |
916 | _write_static_globals self $f $run_nr | |
917 | _write_extern_globals self $f $run_nr "shlib${so_nr}_" $cu_nr | |
918 | _write_static_functions self $f $run_nr | |
919 | _write_extern_functions self $f $run_nr "shlib${so_nr}_" $cu_nr | |
920 | if [_classes_enabled_p self $run_nr] { | |
921 | _write_class_implementations self $f $static $run_nr $so_nr $cu_nr | |
922 | } | |
923 | close $f | |
924 | return $source_file | |
925 | } | |
926 | ||
927 | # CU_NAME is a name from gen_shlib_extra_sources or tail_shlib_sources. | |
928 | ||
929 | proc _make_shlib_common_source_name { self_var static run_nr so_nr cu_name } { | |
930 | upvar 1 $self_var self | |
931 | if { !$static } { | |
932 | set run_name [_get_param $self(run_names) $run_nr] | |
933 | set source_name "${run_name}-lib${so_nr}-${cu_name}" | |
934 | } else { | |
935 | set source_name "lib${so_nr}-${cu_name}" | |
936 | } | |
937 | return "[_make_object_dir_name self $static $run_nr]/[_convert_spaces $source_name]" | |
938 | } | |
939 | ||
940 | # N.B. gdb_compile_shlib doesn't support parallel builds of shlibs from | |
941 | # common sources: the .o file path will be the same across all shlibs. | |
942 | # gen_shlib_extra_sources may be common across all shlibs but they're each | |
943 | # compiled with -DSHLIB=$SHLIB so we need different .o files for each | |
944 | # shlib, and therefore we need different source files for each shlib. | |
945 | # If this turns out to be too cumbersome we can augment gdb_compile_shlib. | |
946 | ||
947 | proc _gen_shlib_common_source { self_var static run_nr so_nr source_name } { | |
948 | upvar 1 $self_var self | |
949 | global srcdir | |
950 | set source_file [_make_shlib_common_source_name self $static $run_nr $so_nr $source_name] | |
951 | file copy -force "$srcdir/gdb.perf/$source_name" ${source_file} | |
952 | return $source_file | |
953 | } | |
954 | ||
955 | # Generate the sources for a shared library. | |
956 | # The result is a list of source and header file names. | |
957 | # E.g., { { source0 source1 ... common0 ... } { header0 header1 ... } } | |
958 | ||
959 | proc _gen_shlib_source { self_var static run_nr so_nr } { | |
960 | upvar 1 $self_var self | |
961 | verbose -log "GenPerfTest::_gen_shlib_source run $run_nr so $so_nr, started [timestamp -format %c]" | |
962 | set headers {} | |
963 | set sources {} | |
964 | set nr_compunits [_get_param $self(nr_compunits) $run_nr] | |
965 | for { set cu_nr 0 } { $cu_nr < $nr_compunits } { incr cu_nr } { | |
966 | lappend headers [_gen_compunit_header self $static $run_nr $so_nr $cu_nr] | |
967 | lappend sources [_gen_shlib_compunit_source self $static $run_nr $so_nr $cu_nr] | |
968 | } | |
969 | foreach source_name [_get_param $self(gen_shlib_extra_sources) $run_nr] { | |
970 | lappend sources [_gen_shlib_common_source self $static $run_nr $so_nr $source_name] | |
971 | } | |
972 | verbose -log "GenPerfTest::_gen_shlib_source run $run_nr so $so_nr, done [timestamp -format %c]" | |
973 | return [list $sources $headers] | |
974 | } | |
975 | ||
976 | # Write Tcl array ARRAY_NAME to F. | |
977 | ||
978 | proc _write_tcl_array { self_var f array_name } { | |
979 | upvar 1 $self_var self | |
980 | if { "$array_name" != "$self_var" } { | |
981 | global $array_name | |
982 | } | |
983 | puts $f "== $array_name ==" | |
984 | foreach { name value } [array get $array_name] { | |
985 | puts $f "$name: $value" | |
986 | } | |
987 | } | |
988 | ||
989 | # Write global Tcl state used for compilation to F. | |
990 | # If anything changes we want to recompile. | |
991 | ||
992 | proc _write_tcl_state { self_var f dest } { | |
993 | upvar 1 $self_var self | |
994 | ||
995 | # TODO(dje): gdb_default_target_compile references a lot of global | |
996 | # state. Can we capture it all? For now these are the important ones. | |
997 | ||
998 | set vars { CC_FOR_TARGET CXX_FOR_TARGET CFLAGS_FOR_TARGET } | |
999 | foreach v $vars { | |
1000 | global $v | |
1001 | if [info exists $v] { | |
1002 | eval set value $$v | |
1003 | puts $f "$v: $value" | |
1004 | } | |
1005 | } | |
1006 | ||
1007 | puts $f "" | |
1008 | _write_tcl_array self $f target_info | |
1009 | puts $f "" | |
1010 | _write_tcl_array self $f board_info | |
1011 | } | |
1012 | ||
1013 | # Write all sideband non-file inputs, as well as OPTIONS to INPUTS_FILE. | |
1014 | # If anything changes we want to recompile. | |
1015 | ||
1016 | proc _write_inputs_file { self_var dest inputs_file options } { | |
1017 | upvar 1 $self_var self | |
1018 | global env | |
1019 | set f [open $inputs_file "w"] | |
1020 | _write_tcl_array self $f self | |
1021 | puts $f "" | |
1022 | puts $f "options: $options" | |
1023 | puts $f "PATH: [getenv PATH]" | |
1024 | puts $f "" | |
1025 | _write_tcl_state self $f $dest | |
1026 | close $f | |
1027 | } | |
1028 | ||
1029 | # Generate the sha1sum of all the inputs. | |
1030 | # The result is a list of { error_code text }. | |
1031 | # Upon success error_code is zero and text is the sha1sum. | |
1032 | # Otherwise, error_code is non_zero and text is the error message. | |
1033 | ||
1034 | proc _gen_sha1sum_for_inputs { source_files header_files inputs } { | |
1035 | global srcdir subdir CAT_PROGRAM SHA1SUM_PROGRAM | |
1036 | set header_paths "" | |
1037 | foreach f $header_files { | |
1038 | switch -glob -- $f { | |
1039 | "<*>" { | |
1040 | # skip | |
1041 | } | |
1042 | "*gdb.perf/outputs/*" { | |
1043 | # in build tree | |
1044 | append header_paths " $f" | |
1045 | } | |
1046 | default { | |
1047 | append header_paths " $srcdir/$subdir/$f" | |
1048 | } | |
1049 | } | |
1050 | } | |
1051 | verbose -log "_gen_sha1sum_for_inputs: summing $source_files $header_paths $inputs" | |
1052 | set catch_result [catch "exec $CAT_PROGRAM $source_files $header_paths $inputs | $SHA1SUM_PROGRAM" output] | |
1053 | return [list $catch_result $output] | |
1054 | } | |
1055 | ||
1056 | # Return the contents of TEXT_FILE. | |
1057 | # It is assumed TEXT_FILE exists and is readable. | |
1058 | # This is used for reading files containing sha1sums, the | |
1059 | # last newline is removed. | |
1060 | ||
1061 | proc _read_file { text_file } { | |
1062 | set f [open $text_file "r"] | |
1063 | set result [read -nonewline $f] | |
1064 | close $f | |
1065 | return $result | |
1066 | } | |
1067 | ||
1068 | # Write TEXT to TEXT_FILE. | |
1069 | # It is assumed TEXT_FILE can be opened/created and written to. | |
1070 | ||
1071 | proc _write_file { text_file text } { | |
1072 | set f [open $text_file "w"] | |
1073 | puts $f $text | |
1074 | close $f | |
1075 | } | |
1076 | ||
1077 | # Wrapper on gdb_compile* that computes sha1sums of inputs to decide | |
1078 | # whether the compile is needed. | |
1079 | # The result is the result of gdb_compile*: "" == success, otherwise | |
1080 | # a compilation error occurred and the output is an error message. | |
1081 | # This doesn't take all inputs into account, just the useful ones. | |
1082 | # As an extension (or simplification) on gdb_compile*, if TYPE is | |
1083 | # shlib then call gdb_compile_shlib, otherwise call gdb_compile. | |
1084 | # Other possibilities *could* be handled this way, e.g., pthreads. TBD. | |
1085 | ||
1086 | proc _perftest_compile { self_var source_files header_files dest type options } { | |
1087 | upvar 1 $self_var self | |
1088 | verbose -log "_perftest_compile $source_files $header_files $dest $type $options" | |
1089 | # To keep things simple, we put all non-file inputs into a file and | |
1090 | # then cat all input files through sha1sum. | |
1091 | set sha1sum_file ${dest}.sha1sum | |
1092 | set sha1new_file ${dest}.sha1new | |
1093 | set inputs_file ${dest}.inputs | |
1094 | global srcdir subdir | |
1095 | set all_options $options | |
1096 | lappend all_options "incdir=$srcdir/$subdir" | |
1097 | _write_inputs_file self $dest $inputs_file $all_options | |
1098 | set sha1sum [_gen_sha1sum_for_inputs $source_files $header_files $inputs_file] | |
1099 | if { [lindex $sha1sum 0] != 0 } { | |
1100 | return "sha1sum generation error: [lindex $sha1sum 1]" | |
1101 | } | |
1102 | set sha1sum [lindex $sha1sum 1] | |
1103 | if ![file exists $dest] { | |
1104 | file delete $sha1sum_file | |
1105 | } | |
1106 | if [file exists $sha1sum_file] { | |
1107 | set last_sha1sum [_read_file $sha1sum_file] | |
1108 | verbose -log "last: $last_sha1sum, new: $sha1sum" | |
1109 | if { $sha1sum == $last_sha1sum } { | |
1110 | verbose -log "using existing build for $dest" | |
1111 | return "" | |
1112 | } | |
1113 | } | |
1114 | # No such luck, we need to compile. | |
1115 | file delete $sha1sum_file | |
1116 | if { $type == "shlib" } { | |
1117 | set result [gdb_compile_shlib $source_files $dest $all_options] | |
1118 | } else { | |
1119 | set result [gdb_compile $source_files $dest $type $all_options] | |
1120 | } | |
1121 | if { $result == "" } { | |
1122 | _write_file $sha1sum_file $sha1sum | |
1123 | verbose -log "wrote sha1sum: $sha1sum" | |
1124 | } | |
1125 | return $result | |
1126 | } | |
1127 | ||
1128 | proc _compile_binary_pieces { self_var worker_nr static run_nr } { | |
1129 | upvar 1 $self_var self | |
1130 | set compile_options [_compile_options self] | |
1131 | set nr_compunits [_get_param $self(nr_compunits) $run_nr] | |
1132 | set extra_headers [_get_param $self(binary_extra_headers) $run_nr] | |
1133 | global PERF_TEST_COMPILE_PARALLELISM | |
1134 | set nr_workers $PERF_TEST_COMPILE_PARALLELISM | |
1135 | # Generate the source first so we can more easily measure how long that | |
1136 | # takes. [It doesn't take hardly any time at all, relative to the time | |
1137 | # it takes to compile it, but this will provide numbers to show that.] | |
1138 | set todo_list [_gen_binary_source self $worker_nr $static $run_nr] | |
1139 | verbose -log "GenPerfTest::_compile_binary_pieces worker $worker_nr run $run_nr, started [timestamp -format %c]" | |
1140 | foreach elm $todo_list { | |
1141 | set source_file [lindex $elm 0] | |
1142 | set header_file [lindex $elm 1] | |
1143 | set object_file [lindex $elm 2] | |
1144 | set all_header_files $extra_headers | |
1145 | lappend all_header_files $header_file | |
1146 | set compile_result [_perftest_compile self $source_file $all_header_files $object_file object $compile_options] | |
1147 | if { $compile_result != "" } { | |
1148 | verbose -log "GenPerfTest::_compile_binary_pieces worker $worker_nr run $run_nr, failed [timestamp -format %c]" | |
1149 | verbose -log $compile_result | |
1150 | return -1 | |
1151 | } | |
1152 | } | |
1153 | verbose -log "GenPerfTest::_compile_binary_pieces worker $worker_nr run $run_nr, done [timestamp -format %c]" | |
1154 | return 0 | |
1155 | } | |
1156 | ||
1157 | # Helper function to compile the pieces of a shlib. | |
1158 | # Note: gdb_compile_shlib{,_pthreads} don't support first building object | |
1159 | # files and then building the shlib. Therefore our hands are tied, and we | |
1160 | # just build the shlib in one step. This is less of a parallelization | |
1161 | # problem if there are multiple shlibs: Each worker can build a different | |
1162 | # shlib. If this proves to be a problem in practice we can enhance | |
1163 | # gdb_compile_shlib* then. | |
1164 | ||
1165 | proc _compile_shlib { self_var static run_nr so_nr } { | |
1166 | upvar 1 $self_var self | |
1167 | set files [_gen_shlib_source self $static $run_nr $so_nr] | |
1168 | set source_files [lindex $files 0] | |
1169 | set header_files [lindex $files 1] | |
1170 | set extra_headers [_get_param $self(gen_shlib_extra_headers) $run_nr] | |
1171 | set shlib_file [_make_shlib_name self $static $run_nr $so_nr] | |
1172 | set compile_options "[_compile_options self] additional_flags=-DSHLIB=$so_nr" | |
1173 | set all_header_files $header_files | |
1174 | append all_header_files $extra_headers | |
1175 | set compile_result [_perftest_compile self $source_files $all_header_files $shlib_file shlib $compile_options] | |
1176 | if { $compile_result != "" } { | |
1177 | verbose -log "_compile_shlib failed: $compile_result" | |
1178 | return -1 | |
1179 | } | |
1180 | return 0 | |
1181 | } | |
1182 | ||
1183 | proc _gen_tail_shlib_source { self_var static run_nr } { | |
1184 | upvar 1 $self_var self | |
1185 | verbose -log "GenPerfTest::_gen_tail_shlib_source run $run_nr" | |
1186 | set source_files [_get_param $self(tail_shlib_sources) $run_nr] | |
1187 | if { [llength $source_files] == 0 } { | |
1188 | return "" | |
1189 | } | |
1190 | set result "" | |
1191 | foreach source_name $source_files { | |
1192 | lappend result [_gen_shlib_common_source self $static $run_nr tail $source_name] | |
1193 | } | |
1194 | return $result | |
1195 | } | |
1196 | ||
1197 | proc _make_tail_shlib_name { self_var static run_nr } { | |
1198 | upvar 1 $self_var self | |
1199 | set source_files [_get_param $self(tail_shlib_sources) $run_nr] | |
1200 | if { [llength $source_files] == 0 } { | |
1201 | return "" | |
1202 | } | |
1203 | return [_make_shlib_name self $static $run_nr "tail"] | |
1204 | } | |
1205 | ||
1206 | # Helper function to compile the tail shlib, if it's specified. | |
1207 | ||
1208 | proc _compile_tail_shlib { self_var static run_nr } { | |
1209 | upvar 1 $self_var self | |
1210 | set source_files [_gen_tail_shlib_source self $static $run_nr] | |
1211 | if { [llength $source_files] == 0 } { | |
1212 | return 0 | |
1213 | } | |
1214 | set header_files [_get_param $self(tail_shlib_headers) $run_nr] | |
1215 | set shlib_file [_make_tail_shlib_name self $static $run_nr] | |
1216 | set compile_options [_compile_options self] | |
1217 | set compile_result [_perftest_compile self $source_files $header_files $shlib_file shlib $compile_options] | |
1218 | if { $compile_result != "" } { | |
1219 | verbose -log "_compile_tail_shlib failed: $compile_result" | |
1220 | return -1 | |
1221 | } | |
1222 | verbose -log "_compile_tail_shlib failed: succeeded" | |
1223 | return 0 | |
1224 | } | |
1225 | ||
1226 | # Compile the pieces of the binary and possible shlibs for the test. | |
1227 | # The result is 0 for success, -1 for failure. | |
1228 | ||
1229 | proc _compile_pieces { self_var worker_nr } { | |
1230 | upvar 1 $self_var self | |
1231 | global PERF_TEST_COMPILE_PARALLELISM | |
1232 | set nr_workers $PERF_TEST_COMPILE_PARALLELISM | |
1233 | set nr_runs [llength $self(run_names)] | |
1234 | set static [_static_object_files_p self] | |
1235 | verbose -log "_compile_pieces: static flag: $static" | |
1236 | file mkdir "[file dirname $self(binfile)]/pieces" | |
1237 | if $static { | |
1238 | # All the generated pieces look the same (run over run) so just | |
1239 | # build all the shlibs of the last run (which is the largest). | |
1240 | set last_run [expr $nr_runs - 1] | |
1241 | set nr_gen_shlibs [_get_param $self(nr_gen_shlibs) $last_run] | |
1242 | set object_dir [_make_object_dir_name self $static ignored] | |
1243 | file mkdir $object_dir | |
1244 | for { set so_nr $worker_nr } { $so_nr < $nr_gen_shlibs } { incr so_nr $nr_workers } { | |
1245 | if { [_compile_shlib self $static $last_run $so_nr] < 0 } { | |
1246 | return -1 | |
1247 | } | |
1248 | } | |
1249 | # We don't shard building of tail-shlib, so only build it once. | |
1250 | if { $worker_nr == 0 } { | |
1251 | if { [_compile_tail_shlib self $static $last_run] < 0 } { | |
1252 | return -1 | |
1253 | } | |
1254 | } | |
1255 | if { [_compile_binary_pieces self $worker_nr $static $last_run] < 0 } { | |
1256 | return -1 | |
1257 | } | |
1258 | } else { | |
1259 | for { set run_nr 0 } { $run_nr < $nr_runs } { incr run_nr } { | |
1260 | set nr_gen_shlibs [_get_param $self(nr_gen_shlibs) $run_nr] | |
1261 | set object_dir [_make_object_dir_name self $static $run_nr] | |
1262 | file mkdir $object_dir | |
1263 | for { set so_nr $worker_nr } { $so_nr < $nr_gen_shlibs } { incr so_nr $nr_workers } { | |
1264 | if { [_compile_shlib self $static $run_nr $so_nr] < 0 } { | |
1265 | return -1 | |
1266 | } | |
1267 | } | |
1268 | # We don't shard building of tail-shlib, so only build it once. | |
1269 | if { $worker_nr == 0 } { | |
1270 | if { [_compile_tail_shlib self $static $run_nr] < 0 } { | |
1271 | return -1 | |
1272 | } | |
1273 | } | |
1274 | if { [_compile_binary_pieces self $worker_nr $static $run_nr] < 0 } { | |
1275 | return -1 | |
1276 | } | |
1277 | } | |
1278 | } | |
1279 | return 0 | |
1280 | } | |
1281 | ||
1282 | # Main function invoked by each worker. | |
1283 | # This builds all the things that are possible to build in parallel, | |
1284 | # sharded up among all the workers. | |
1285 | ||
1286 | proc compile_pieces { self_var worker_nr } { | |
1287 | upvar 1 $self_var self | |
1288 | verbose -log "GenPerfTest::compile_pieces worker $worker_nr, started [timestamp -format %c]" | |
1289 | verbose -log "self: [array get self]" | |
1290 | _verify_testcase self | |
1291 | if { [_compile_pieces self $worker_nr] < 0 } { | |
1292 | verbose -log "GenPerfTest::compile_pieces worker $worker_nr, failed [timestamp -format %c]" | |
1293 | return -1 | |
1294 | } | |
1295 | verbose -log "GenPerfTest::compile_pieces worker $worker_nr, done [timestamp -format %c]" | |
1296 | return 0 | |
1297 | } | |
1298 | ||
1299 | proc _make_shlib_options { self_var static run_nr } { | |
1300 | upvar 1 $self_var self | |
1301 | set nr_gen_shlibs [_get_param $self(nr_gen_shlibs) $run_nr] | |
1302 | set result "" | |
1303 | for { set i 0 } { $i < $nr_gen_shlibs } { incr i } { | |
1304 | lappend result "shlib=[_make_shlib_name self $static $run_nr $i]" | |
1305 | } | |
1306 | set tail_shlib_name [_make_tail_shlib_name self $static $run_nr] | |
1307 | if { "$tail_shlib_name" != "" } { | |
1308 | lappend result "shlib=$tail_shlib_name" | |
1309 | } | |
1310 | return $result | |
1311 | } | |
1312 | ||
1313 | proc _compile_binary { self_var static run_nr } { | |
1314 | upvar 1 $self_var self | |
1315 | set input_files [_make_binary_input_file_names self $static $run_nr] | |
1316 | set extra_headers [_get_param $self(binary_extra_headers) $run_nr] | |
1317 | set binary_file [_make_binary_name self $run_nr] | |
1318 | set compile_options [_compile_options self] | |
1319 | set shlib_options [_make_shlib_options self $static $run_nr] | |
1320 | if { [llength $shlib_options] > 0 } { | |
1321 | append compile_options " " $shlib_options | |
1322 | } | |
1323 | set compile_result [_perftest_compile self $input_files $extra_headers $binary_file executable $compile_options] | |
1324 | if { $compile_result != "" } { | |
1325 | verbose -log "_compile_binary failed: $compile_result" | |
1326 | return -1 | |
1327 | } | |
1328 | return 0 | |
1329 | } | |
1330 | ||
1331 | # Helper function for compile. | |
1332 | # The result is 0 for success, -1 for failure. | |
1333 | ||
1334 | proc _compile { self_var } { | |
1335 | upvar 1 $self_var self | |
1336 | set nr_runs [llength $self(run_names)] | |
1337 | set static [_static_object_files_p self] | |
1338 | verbose -log "_compile: static flag: $static" | |
1339 | for { set run_nr 0 } { $run_nr < $nr_runs } { incr run_nr } { | |
1340 | if { [_compile_binary self $static $run_nr] < 0 } { | |
1341 | return -1 | |
1342 | } | |
1343 | } | |
1344 | return 0 | |
1345 | } | |
1346 | ||
1347 | # Main function to compile the test program. | |
1348 | # It is assumed all the pieces of the binary (all the .o's, except those | |
1349 | # from test-supplied sources) have already been built with compile_pieces. | |
1350 | # There's no need to compile any shlibs here, as compile_pieces will have | |
1351 | # already built them too. | |
1352 | # The result is 0 for success, -1 for failure. | |
1353 | ||
1354 | proc compile { self_var } { | |
1355 | upvar 1 $self_var self | |
1356 | verbose -log "GenPerfTest::compile, started [timestamp -format %c]" | |
1357 | verbose -log "self: [array get self]" | |
1358 | _verify_testcase self | |
1359 | if { [_compile self] < 0 } { | |
1360 | verbose -log "GenPerfTest::compile, failed [timestamp -format %c]" | |
1361 | return -1 | |
1362 | } | |
1363 | verbose -log "GenPerfTest::compile, done [timestamp -format %c]" | |
1364 | return 0 | |
1365 | } | |
1366 | ||
1367 | # Main function for running a test. | |
1368 | # It is assumed that the test program has already been built. | |
1369 | ||
1370 | proc run { builder_exp_file_name make_config_thunk_name py_file_name test_class_name } { | |
1371 | verbose -log "GenPerfTest::run, started [timestamp -format %c]" | |
1372 | verbose -log "GenPerfTest::run, $builder_exp_file_name $make_config_thunk_name $py_file_name $test_class_name" | |
1373 | ||
1374 | set testprog [file rootname $builder_exp_file_name] | |
1375 | ||
1376 | # This variable is required by perftest.exp. | |
1377 | # This isn't the name of the test program, it's the name of the .py | |
1378 | # test. The harness assumes they are the same, which is not the case | |
1379 | # here. | |
1380 | global testfile | |
1381 | set testfile [file rootname $py_file_name] | |
1382 | ||
1383 | GenPerfTest::load_test_description $builder_exp_file_name | |
1384 | ||
1385 | array set testcase [$make_config_thunk_name] | |
1386 | ||
1387 | PerfTest::assemble { | |
1388 | # Compilation is handled elsewhere. | |
1389 | return 0 | |
1390 | } { | |
1391 | clean_restart | |
1392 | return 0 | |
1393 | } { | |
1394 | global gdb_prompt | |
1395 | gdb_test_multiple "python ${test_class_name}('$testprog:$testfile', [tcl_string_list_to_python_list $testcase(run_names)], '$testcase(binfile)').run()" "run test" { | |
1396 | -re "Error while executing Python code.\[\r\n\]+$gdb_prompt $" { | |
1397 | return -1 | |
1398 | } | |
1399 | -re "\[\r\n\]+$gdb_prompt $" { | |
1400 | } | |
1401 | } | |
1402 | return 0 | |
1403 | } | |
1404 | verbose -log "GenPerfTest::run, done [timestamp -format %c]" | |
1405 | return 0 | |
1406 | } | |
1407 | ||
1408 | # This function is invoked by the testcase builder scripts | |
1409 | # (e.g., gmonster[12].exp). | |
1410 | # It is not invoked by the testcase runner scripts | |
1411 | # (e.g., gmonster[12]-*.exp). | |
1412 | ||
1413 | proc standard_compile_driver { exp_file_name make_config_thunk_name } { | |
1414 | global GDB_PERFTEST_MODE GDB_PERFTEST_SUBMODE | |
1415 | if ![info exists GDB_PERFTEST_SUBMODE] { | |
1416 | # Probably a plain "make check-perf", nothing to do. | |
1417 | # Give the user a reason why we're not running this test. | |
1418 | verbose -log "Test must be compiled/run in separate steps." | |
1419 | return 0 | |
1420 | } | |
1421 | switch -glob -- "$GDB_PERFTEST_MODE/$GDB_PERFTEST_SUBMODE" { | |
1422 | compile/gen-workers { | |
1423 | if { [GenPerfTest::gen_worker_files $exp_file_name] < 0 } { | |
1424 | fail $GDB_PERFTEST_MODE | |
1425 | return -1 | |
1426 | } | |
1427 | pass $GDB_PERFTEST_MODE | |
1428 | } | |
1429 | compile/build-pieces { | |
1430 | array set testcase [$make_config_thunk_name] | |
1431 | global PROGRAM_NAME WORKER_NR | |
1432 | if { [GenPerfTest::compile_pieces testcase $WORKER_NR] < 0 } { | |
1433 | fail $GDB_PERFTEST_MODE | |
1434 | # This gdb.log lives in a different place, help the user | |
1435 | # find it. | |
1436 | set output_dir "gdb.perf/outputs" | |
1437 | send_user "check ${output_dir}/${PROGRAM_NAME}/${PROGRAM_NAME}-${WORKER_NR}/gdb.log\n" | |
1438 | return -1 | |
1439 | } | |
1440 | pass $GDB_PERFTEST_MODE | |
1441 | } | |
1442 | compile/final { | |
1443 | array set testcase [$make_config_thunk_name] | |
1444 | if { [GenPerfTest::compile testcase] < 0 } { | |
1445 | fail $GDB_PERFTEST_MODE | |
1446 | return -1 | |
1447 | } | |
1448 | pass $GDB_PERFTEST_MODE | |
1449 | } | |
1450 | run/* - both/* { | |
1451 | # Since the builder script is a .exp file living in gdb.perf | |
1452 | # we can get here (dejagnu will find this file for a default | |
1453 | # "make check-perf"). We can also get here when | |
1454 | # standard_run_driver loads the builder .exp file. | |
1455 | } | |
1456 | default { | |
1457 | error "Bad value for GDB_PERFTEST_MODE/GDB_PERFTEST_SUBMODE: $GDB_PERFTEST_MODE/$GDB_PERFTEST_SUBMODE" | |
1458 | } | |
1459 | } | |
1460 | return 0 | |
1461 | } | |
1462 | ||
1463 | # This function is invoked by the testcase runner scripts | |
1464 | # (e.g., gmonster[12]-*.exp). | |
1465 | # It is not invoked by the testcase builder scripts | |
1466 | # (e.g., gmonster[12].exp). | |
1467 | # | |
1468 | # These tests are built separately with | |
1469 | # "make build-perf" and run with | |
1470 | # "make check-perf GDB_PERFTEST_MODE=run". | |
1471 | # Eventually we can support GDB_PERFTEST_MODE=both, but for now we don't. | |
1472 | ||
1473 | proc standard_run_driver { builder_exp_file_name make_config_thunk_name py_file_name test_class_name } { | |
1474 | global GDB_PERFTEST_MODE | |
1475 | # First step is to compile the test. | |
1476 | switch $GDB_PERFTEST_MODE { | |
1477 | compile - both { | |
1478 | # Here is where we'd add code to support a plain | |
1479 | # "make check-perf". | |
1480 | } | |
1481 | run { | |
1482 | } | |
1483 | default { | |
1484 | error "Bad value for GDB_PERFTEST_MODE: $GDB_PERFTEST_MODE" | |
1485 | } | |
1486 | } | |
1487 | # Now run the test. | |
1488 | switch $GDB_PERFTEST_MODE { | |
1489 | compile { | |
1490 | } | |
1491 | both { | |
1492 | # Give the user a reason why we're not running this test. | |
1493 | verbose -log "Test must be compiled/run in separate steps." | |
1494 | } | |
1495 | run { | |
1496 | if { [GenPerfTest::run $builder_exp_file_name $make_config_thunk_name $py_file_name $test_class_name] < 0 } { | |
1497 | fail $GDB_PERFTEST_MODE | |
1498 | return -1 | |
1499 | } | |
1500 | pass $GDB_PERFTEST_MODE | |
1501 | } | |
1502 | } | |
1503 | return 0 | |
1504 | } | |
1505 | } | |
1506 | ||
1507 | if ![info exists PERF_TEST_COMPILE_PARALLELISM] { | |
1508 | set PERF_TEST_COMPILE_PARALLELISM $GenPerfTest::DEFAULT_PERF_TEST_COMPILE_PARALLELISM | |
1509 | } |