| # Copyright (C) 2013-2018 Free Software Foundation, Inc. |
| |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 3 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| # |
| # Notes: |
| # 1) This follows a Python convention for marking internal vs public functions. |
| # Internal functions are prefixed with "_". |
| |
| # A simple testcase generator. |
| # |
| # Usage Notes: |
| # |
| # 1) The length of each parameter list must either be one, in which case the |
| # same value is used for each run, or the length must match all other |
| # parameters of length greater than one. |
| # |
| # 2) Values for parameters that vary across runs must appear in increasing |
| # order. E.g. nr_gen_shlibs = { 0 1 10 } is good, { 1 0 10 } is bad. |
| # This rule simplifies the code a bit, without being onerous on the user: |
| # a) Report generation doesn't have to sort the output by run, it'll already |
| # be sorted. |
| # b) In the static object file case, the last run can be used used to generate |
| # all the source files. |
| # |
| # TODO: |
| # 1) have functions call each other within an objfile and across |
| # objfiles to measure things like backtrace times |
| # 2) enums |
| # |
| # Implementation Notes: |
| # |
| # 1) The implementation would be a bit simpler if we could assume Tcl 8.5. |
| # Then we could use a dictionary to record the testcase instead of an array. |
| # With the array we use here, there is only one copy of it and instead of |
| # passing its value we pass its name. Yay Tcl. An alternative is to just |
| # use a global variable. |
| # |
| # 2) Because these programs can be rather large, we try to avoid recompilation |
| # where we can. We don't have a makefile: we could generate one but it's |
| # not clear that's simpler than our chosen mechanism which is to record |
| # sums of all the inputs, and detect if an input has changed that way. |
| |
| if ![info exists CAT_PROGRAM] { |
| set CAT_PROGRAM "/bin/cat" |
| } |
| |
| # TODO(dje): Time md5sum vs sha1sum with our testcases. |
| if ![info exists SHA1SUM_PROGRAM] { |
| set SHA1SUM_PROGRAM "/usr/bin/sha1sum" |
| } |
| |
| namespace eval GenPerfTest { |
| |
| # The default level of compilation parallelism we support. |
| set DEFAULT_PERF_TEST_COMPILE_PARALLELISM 10 |
| |
| # The language of the test. |
| set DEFAULT_LANGUAGE "c" |
| |
| # Extra source files for the binary. |
| # This must at least include the file with main(), |
| # and each test must supply its own. |
| set DEFAULT_BINARY_EXTRA_SOURCES {} |
| |
| # Header files used by generated files and extra sources. |
| set DEFAULT_BINARY_EXTRA_HEADERS {} |
| |
| # Extra source files for each generated shlib. |
| # The compiler passes -DSHLIB=NNN which the source can use, for example, |
| # to define unique symbols for each shlib. |
| set DEFAULT_GEN_SHLIB_EXTRA_SOURCES {} |
| |
| # Header files used by generated files and extra sources. |
| set DEFAULT_GEN_SHLIB_EXTRA_HEADERS {} |
| |
| # Source files for a tail shlib, or empty if none. |
| # This library is loaded after all other shlibs (except any system shlibs |
| # like libstdc++). It is useful for exercising issues that can appear |
| # with system shlibs, without having to cope with implementation details |
| # and bugs in system shlibs. E.g., gcc pr 65669. |
| set DEFAULT_TAIL_SHLIB_SOURCES {} |
| |
| # Header files for the tail shlib. |
| set DEFAULT_TAIL_SHLIB_HEADERS {} |
| |
| # The number of shared libraries to create. |
| set DEFAULT_NR_GEN_SHLIBS 0 |
| |
| # The number of compunits in each objfile. |
| set DEFAULT_NR_COMPUNITS 1 |
| |
| # The number of public globals in each compunit. |
| set DEFAULT_NR_EXTERN_GLOBALS 1 |
| |
| # The number of static globals in each compunit. |
| set DEFAULT_NR_STATIC_GLOBALS 1 |
| |
| # The number of public functions in each compunit. |
| set DEFAULT_NR_EXTERN_FUNCTIONS 1 |
| |
| # The number of static functions in each compunit. |
| set DEFAULT_NR_STATIC_FUNCTIONS 1 |
| |
| # Class generation. |
| # This is only used if the selected language permits it. |
| # The class specs here are used for each compunit. |
| # Additional flexibility can be added as needed, but for now KISS. |
| # |
| # key/value list of: |
| # count: number of classes |
| # Default: 1 |
| # name: list of namespaces and class name prefix |
| # E.g., { ns0 ns1 foo } -> ns0::ns1::foo_<cu#>_{0,1,...} |
| # There is no default, this value must be specified. |
| # nr_members: number of members |
| # Default: 0 |
| # nr_static_members: number of static members |
| # Default: 0 |
| # nr_methods: number of methods |
| # Default: 0 |
| # nr_inline_methods: number of inline methods |
| # Default: 0 |
| # nr_static_methods: number of static methods |
| # Default: 0 |
| # nr_static_inline_methods: number of static inline methods |
| # Default: 0 |
| # |
| # E.g., |
| # class foo {}; |
| # namespace ns1 { class foo {}; } |
| # namespace ns2 { class bar {}; } |
| # would be represented as |
| # { |
| # { count 1 name { foo } } |
| # { count 1 name { ns1 foo } } |
| # { count 1 name { ns2 bar } } |
| # } |
| # The actual generated class names will be |
| # cu_N_foo_0, ns1::cu_N_foo_0, ns2::cu_N_bar_0 |
| # where "N" is the CU number. |
| # |
| # To keep things simple for now, all class definitions go in headers, |
| # one class per header, with non-inline method definitions going |
| # into corresponding source files. |
| set DEFAULT_CLASS_SPECS {} |
| |
| # The default value for the "count" field of class_specs. |
| set DEFAULT_CLASS_COUNT 1 |
| |
| # The default number of members in each class. |
| set DEFAULT_CLASS_NR_MEMBERS 0 |
| |
| # The default number of static members in each class. |
| set DEFAULT_CLASS_NR_STATIC_MEMBERS 0 |
| |
| # The default number of methods in each class. |
| set DEFAULT_CLASS_NR_METHODS 0 |
| |
| # The default number of inline methods in each class. |
| set DEFAULT_CLASS_NR_INLINE_METHODS 0 |
| |
| # The default number of static methods in each class. |
| set DEFAULT_CLASS_NR_STATIC_METHODS 0 |
| |
| # The default number of static inline methods in each class. |
| set DEFAULT_CLASS_NR_STATIC_INLINE_METHODS 0 |
| |
| set header_suffixes(c) "h" |
| set header_suffixes(c++) "h" |
| set source_suffixes(c) "c" |
| set source_suffixes(c++) "cc" |
| |
| # Generate .worker files that control building all the "pieces" of the |
| # testcase. This doesn't include "main" or any test-specific stuff. |
| # This mostly consists of the "bulk" (aka "crap" :-)) of the testcase to |
| # give gdb something meaty to chew on. |
| # The result is 0 for success, -1 for failure. |
| # |
| # Benchmarks generated by some of the tests are big. I mean really big. |
| # And it's a pain to build one piece at a time, we need a parallel build. |
| # To achieve this, given the framework we're working with, we need to |
| # generate arguments to pass to a parallel make. This is done by |
| # generating several files and then passing the file names to the parallel |
| # make. All of the needed info is contained in the file name, so we could |
| # do this differently, but this is pretty simple and flexible. |
| |
| proc gen_worker_files { test_description_exp } { |
| global objdir PERF_TEST_COMPILE_PARALLELISM |
| |
| if { [file tail $test_description_exp] != $test_description_exp } { |
| error "test description file contains directory name" |
| } |
| |
| set program_name [file rootname $test_description_exp] |
| set workers_dir "$objdir/gdb.perf/workers/$program_name" |
| file mkdir $workers_dir |
| |
| set nr_workers $PERF_TEST_COMPILE_PARALLELISM |
| verbose -log "gen_worker_files: $test_description_exp $nr_workers workers" |
| |
| for { set i 0 } { $i < $nr_workers } { incr i } { |
| set file_name "${workers_dir}/${program_name}-${i}.worker" |
| verbose -log "gen_worker_files: Generating $file_name" |
| set f [open $file_name "w"] |
| puts $f "# DO NOT EDIT, machine generated file." |
| puts $f "# See perftest.exp:GenPerfTest::gen_worker_files." |
| close $f |
| } |
| |
| return 0 |
| } |
| |
| # Load a perftest description. |
| # Test descriptions are used to build the input files (binary + shlibs) |
| # of one or more performance tests. |
| |
| proc load_test_description { basename } { |
| global srcdir |
| |
| if { [file tail $basename] != $basename } { |
| error "test description file contains directory name" |
| } |
| |
| verbose -log "load_file $srcdir/gdb.perf/$basename" |
| if { [load_file $srcdir/gdb.perf/$basename] == 0 } { |
| error "Unable to load test description $basename" |
| } |
| } |
| |
| # Create a testcase object for test NAME. |
| # The caller must call this as: |
| # array set my_test [GenPerfTest::init_testcase $name] |
| |
| proc init_testcase { name } { |
| set testcase(name) $name |
| set testcase(language) $GenPerfTest::DEFAULT_LANGUAGE |
| set testcase(run_names) [list $name] |
| set testcase(binary_extra_sources) $GenPerfTest::DEFAULT_BINARY_EXTRA_SOURCES |
| set testcase(binary_extra_headers) $GenPerfTest::DEFAULT_BINARY_EXTRA_HEADERS |
| set testcase(gen_shlib_extra_sources) $GenPerfTest::DEFAULT_GEN_SHLIB_EXTRA_SOURCES |
| set testcase(gen_shlib_extra_headers) $GenPerfTest::DEFAULT_GEN_SHLIB_EXTRA_HEADERS |
| set testcase(tail_shlib_sources) $GenPerfTest::DEFAULT_TAIL_SHLIB_SOURCES |
| set testcase(tail_shlib_headers) $GenPerfTest::DEFAULT_TAIL_SHLIB_HEADERS |
| set testcase(nr_gen_shlibs) $GenPerfTest::DEFAULT_NR_GEN_SHLIBS |
| set testcase(nr_compunits) $GenPerfTest::DEFAULT_NR_COMPUNITS |
| |
| set testcase(nr_extern_globals) $GenPerfTest::DEFAULT_NR_EXTERN_GLOBALS |
| set testcase(nr_static_globals) $GenPerfTest::DEFAULT_NR_STATIC_GLOBALS |
| set testcase(nr_extern_functions) $GenPerfTest::DEFAULT_NR_EXTERN_FUNCTIONS |
| set testcase(nr_static_functions) $GenPerfTest::DEFAULT_NR_STATIC_FUNCTIONS |
| |
| set testcase(class_specs) $GenPerfTest::DEFAULT_CLASS_SPECS |
| |
| # The location of this file drives the location of all other files. |
| # The choice is derived from standard_output_file. We don't use it |
| # because of the parallel build support, we want each worker's log/sum |
| # files to go in different directories, but we don't want their output |
| # to go in different directories. |
| # N.B. The value here must be kept in sync with Makefile.in. |
| global objdir |
| set name_no_spaces [_convert_spaces $name] |
| set testcase(binfile) "$objdir/gdb.perf/outputs/$name_no_spaces/$name_no_spaces" |
| |
| return [array get testcase] |
| } |
| |
| proc _verify_parameter_lengths { self_var } { |
| upvar 1 $self_var self |
| set params { |
| binary_extra_sources binary_extra_headers |
| gen_shlib_extra_sources gen_shlib_extra_headers |
| tail_shlib_sources tail_shlib_headers |
| nr_gen_shlibs nr_compunits |
| nr_extern_globals nr_static_globals |
| nr_extern_functions nr_static_functions |
| class_specs |
| } |
| set nr_runs [llength $self(run_names)] |
| foreach p $params { |
| set n [llength $self($p)] |
| if { $n > 1 } { |
| if { $n != $nr_runs } { |
| error "Bad number of values for parameter $p" |
| } |
| set values $self($p) |
| for { set i 0 } { $i < $n - 1 } { incr i } { |
| if { [lindex $values $i] > [lindex $values [expr $i + 1]] } { |
| error "Values of parameter $p are not increasing" |
| } |
| } |
| } |
| } |
| } |
| |
| # Verify the class_specs parameter. |
| |
| proc _verify_class_specs { self_var } { |
| upvar 1 $self_var self |
| set nr_runs [llength $self(run_names)] |
| for { set run_nr 0 } { $run_nr < $nr_runs } { incr run_nr } { |
| set class_specs [_get_param $self(class_specs) $run_nr] |
| foreach { spec } $class_specs { |
| if { [llength $spec] % 2 != 0 } { |
| error "Uneven number of values in class spec: $spec" |
| } |
| foreach { key value } $spec { |
| switch -exact -- $key { |
| name { } |
| count - |
| nr_members - nr_static_members - |
| nr_methods - nr_static_methods - |
| nr_inline_methods - nr_static_inline_methods |
| { |
| if ![string is integer $value] { |
| error "Non-integer value $value for key $key in class_specs: $class_specs" |
| } |
| } |
| default { |
| error "Unrecognized key $key in class_specs: $class_specs" |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| # Verify the testcase is valid (as best we can, this isn't exhaustive). |
| |
| proc _verify_testcase { self_var } { |
| upvar 1 $self_var self |
| _verify_parameter_lengths self |
| _verify_class_specs self |
| |
| # Each test must supply its own main(). We don't check for main here, |
| # but we do verify the test supplied something. |
| if { [llength $self(binary_extra_sources)] == 0 } { |
| error "Missing value for binary_extra_sources" |
| } |
| } |
| |
| # Return the value of parameter PARAM for run RUN_NR. |
| |
| proc _get_param { param run_nr } { |
| if { [llength $param] == 1 } { |
| # Since PARAM may be a list of lists we need to use lindex. This |
| # also works for scalars (scalars are degenerate lists). |
| return [lindex $param 0] |
| } |
| return [lindex $param $run_nr] |
| } |
| |
| # Return non-zero if all files (binaries + shlibs) can be compiled from |
| # one set of object files. This is a simple optimization to speed up |
| # test build times. This happens if the only variation among runs is |
| # nr_gen_shlibs or nr_compunits. |
| |
| proc _static_object_files_p { self_var } { |
| upvar 1 $self_var self |
| # These values are either scalars, or can vary across runs but don't |
| # affect whether we can share the generated object objects between |
| # runs. |
| set static_object_file_params { |
| name language run_names nr_gen_shlibs nr_compunits |
| binary_extra_sources gen_shlib_extra_sources tail_shlib_sources |
| } |
| foreach name [array names self] { |
| if { [lsearch $static_object_file_params $name] < 0 } { |
| # name is not in static_object_file_params. |
| if { [llength $self($name)] > 1 } { |
| # The user could provide a list that is all the same value, |
| # so check for that. |
| set first_value [lindex $self($name) 0] |
| foreach elm [lrange $self($name) 1 end] { |
| if { $elm != $first_value } { |
| return 0 |
| } |
| } |
| } |
| } |
| } |
| return 1 |
| } |
| |
| # Return non-zero if classes are enabled. |
| |
| proc _classes_enabled_p { self_var run_nr } { |
| upvar 1 $self_var self |
| set class_specs [_get_param $self(class_specs) $run_nr] |
| return [expr [llength $class_specs] > 0] |
| } |
| |
| # Spaces in file names are a pain, remove them. |
| # They appear if the user puts spaces in the test name or run name. |
| |
| proc _convert_spaces { file_name } { |
| return [regsub -all " " $file_name "-"] |
| } |
| |
| # Return the compilation flags for the test. |
| |
| proc _compile_options { self_var } { |
| upvar 1 $self_var self |
| set result {debug} |
| switch $self(language) { |
| c++ { |
| lappend result "c++" |
| } |
| } |
| return $result |
| } |
| |
| # Return the path to put source/object files in for run number RUN_NR. |
| |
| proc _make_object_dir_name { self_var static run_nr } { |
| upvar 1 $self_var self |
| # Note: The output directory already includes the name of the test |
| # description file. |
| set bindir [file dirname $self(binfile)] |
| # Put the pieces in a subdirectory, there are a lot of them. |
| if $static { |
| return "$bindir/pieces" |
| } else { |
| set run_name [_convert_spaces [lindex $self(run_names) $run_nr]] |
| return "$bindir/pieces/$run_name" |
| } |
| } |
| |
| # RUN_NR is ignored if STATIC is non-zero. |
| # SO_NR is the shlib number or "" for the binary. |
| # CU_NR is either the compilation unit number or "main". |
| |
| proc _make_header_basename { self_var static run_nr so_nr cu_nr } { |
| upvar 1 $self_var self |
| set header_suffix $GenPerfTest::header_suffixes($self(language)) |
| if { !$static } { |
| set run_name [_get_param $self(run_names) $run_nr] |
| if { "$so_nr" != "" } { |
| set header_name "${run_name}-lib${so_nr}-cu${cu_nr}.$header_suffix" |
| } else { |
| set header_name "${run_name}-cu${cu_nr}.$header_suffix" |
| } |
| } else { |
| if { "$so_nr" != "" } { |
| set header_name "lib${so_nr}-cu${cu_nr}.$header_suffix" |
| } else { |
| set header_name "cu${cu_nr}.$header_suffix" |
| } |
| } |
| return "[_convert_spaces $header_name]" |
| } |
| |
| # RUN_NR is ignored if STATIC is non-zero. |
| # SO_NR is the shlib number or "" for the binary. |
| # CU_NR is either the compilation unit number or "main". |
| |
| proc _make_header_name { self_var static run_nr so_nr cu_nr } { |
| upvar 1 $self_var self |
| set header_name [_make_header_basename self $static $run_nr $so_nr $cu_nr] |
| return "[_make_object_dir_name self $static $run_nr]/$header_name" |
| } |
| |
| # RUN_NR is ignored if STATIC is non-zero. |
| # SO_NR is the shlib number or "" for the binary. |
| # CU_NR is either the compilation unit number or "main". |
| |
| proc _make_source_basename { self_var static run_nr so_nr cu_nr } { |
| upvar 1 $self_var self |
| set source_suffix $GenPerfTest::source_suffixes($self(language)) |
| if { !$static } { |
| set run_name [_get_param $self(run_names) $run_nr] |
| if { "$so_nr" != "" } { |
| set source_name "${run_name}-lib${so_nr}-cu${cu_nr}.$source_suffix" |
| } else { |
| set source_name "${run_name}-cu${cu_nr}.$source_suffix" |
| } |
| } else { |
| if { "$so_nr" != "" } { |
| set source_name "lib${so_nr}-cu${cu_nr}.$source_suffix" |
| } else { |
| set source_name "cu${cu_nr}.$source_suffix" |
| } |
| } |
| return "[_convert_spaces $source_name]" |
| } |
| |
| # RUN_NR is ignored if STATIC is non-zero. |
| # SO_NR is the shlib number or "" for the binary. |
| # CU_NR is either the compilation unit number or "main". |
| |
| proc _make_source_name { self_var static run_nr so_nr cu_nr } { |
| upvar 1 $self_var self |
| set source_name [_make_source_basename self $static $run_nr $so_nr $cu_nr] |
| return "[_make_object_dir_name self $static $run_nr]/$source_name" |
| } |
| |
| # Generated object files get put in the same directory as their source. |
| # WARNING: This means that we can't do parallel compiles from the same |
| # source file, they have to have different names. |
| |
| proc _make_binary_object_name { self_var static run_nr cu_nr } { |
| upvar 1 $self_var self |
| set source_name [_make_source_name self $static $run_nr "" $cu_nr] |
| return [file rootname $source_name].o |
| } |
| |
| # Return the list of source/object files for the binary. |
| # This is the source files specified in test param binary_extra_sources as |
| # well as the names of all the object file "pieces". |
| # STATIC is the value of _static_object_files_p for the test. |
| |
| proc _make_binary_input_file_names { self_var static run_nr } { |
| upvar 1 $self_var self |
| global srcdir subdir |
| set nr_compunits [_get_param $self(nr_compunits) $run_nr] |
| set result {} |
| foreach source [_get_param $self(binary_extra_sources) $run_nr] { |
| lappend result "$srcdir/$subdir/$source" |
| } |
| for { set cu_nr 0 } { $cu_nr < $nr_compunits } { incr cu_nr } { |
| lappend result [_make_binary_object_name self $static $run_nr $cu_nr] |
| } |
| return $result |
| } |
| |
| proc _make_binary_name { self_var run_nr } { |
| upvar 1 $self_var self |
| set run_name [_get_param $self(run_names) $run_nr] |
| set exe_name "$self(binfile)-[_convert_spaces ${run_name}]" |
| return $exe_name |
| } |
| |
| # SO_NAME is either a shlib number or "tail". |
| |
| proc _make_shlib_name { self_var static run_nr so_name } { |
| upvar 1 $self_var self |
| if { !$static } { |
| set run_name [_get_param $self(run_names) $run_nr] |
| set lib_name "$self(name)-${run_name}-lib${so_name}.so" |
| } else { |
| set lib_name "$self(name)-lib${so_name}.so" |
| } |
| set output_dir [file dirname $self(binfile)] |
| return "[_make_object_dir_name self $static $run_nr]/[_convert_spaces $lib_name]" |
| } |
| |
| proc _create_file { self_var path } { |
| upvar 1 $self_var self |
| verbose -log "Creating file: $path" |
| set f [open $path "w"] |
| return $f |
| } |
| |
| proc _write_intro { self_var f } { |
| upvar 1 $self_var self |
| puts $f "// DO NOT EDIT, machine generated file." |
| puts $f "// See perftest.exp:GenPerfTest." |
| } |
| |
| proc _write_includes { self_var f includes } { |
| upvar 1 $self_var self |
| if { [llength $includes] > 0 } { |
| puts $f "" |
| } |
| foreach i $includes { |
| switch -glob -- $i { |
| "<*>" { |
| puts $f "#include $i" |
| } |
| default { |
| puts $f "#include \"$i\"" |
| } |
| } |
| } |
| } |
| |
| proc _make_header_macro { name c } { |
| return [string toupper "${name}_${c}"] |
| } |
| |
| proc _write_static_globals { self_var f run_nr } { |
| upvar 1 $self_var self |
| puts $f "" |
| set nr_static_globals [_get_param $self(nr_static_globals) $run_nr] |
| # Rather than parameterize the number of const/non-const globals, |
| # and their types, we keep it simple for now. Even the number of |
| # bss/non-bss globals may be useful; later, if warranted. |
| for { set i 0 } { $i < $nr_static_globals } { incr i } { |
| if { $i % 2 == 0 } { |
| set const "const " |
| } else { |
| set const "" |
| } |
| puts $f "static ${const}int static_global_$i = $i;" |
| } |
| } |
| |
| # ID is "" for the binary, and a unique symbol prefix for each SO. |
| |
| proc _write_extern_globals { self_var f run_nr id cu_nr } { |
| upvar 1 $self_var self |
| puts $f "" |
| set nr_extern_globals [_get_param $self(nr_extern_globals) $run_nr] |
| # Rather than parameterize the number of const/non-const globals, |
| # and their types, we keep it simple for now. Even the number of |
| # bss/non-bss globals may be useful; later, if warranted. |
| for { set i 0 } { $i < $nr_extern_globals } { incr i } { |
| if { $i % 2 == 0 } { |
| set const "const " |
| } else { |
| set const "" |
| } |
| puts $f "${const}int ${id}global_${cu_nr}_$i = $cu_nr * 1000 + $i;" |
| } |
| } |
| |
| proc _write_static_functions { self_var f run_nr } { |
| upvar 1 $self_var self |
| set nr_static_functions [_get_param $self(nr_static_functions) $run_nr] |
| for { set i 0 } { $i < $nr_static_functions } { incr i } { |
| puts $f "" |
| puts $f "static void" |
| puts $f "static_function_$i (void)" |
| puts $f "{" |
| puts $f "}" |
| } |
| } |
| |
| # ID is "" for the binary, and a unique symbol prefix for each SO. |
| |
| proc _write_extern_functions { self_var f run_nr id cu_nr } { |
| upvar 1 $self_var self |
| set nr_extern_functions [_get_param $self(nr_extern_functions) $run_nr] |
| for { set i 0 } { $i < $nr_extern_functions } { incr i } { |
| puts $f "" |
| puts $f "void" |
| puts $f "${id}function_${cu_nr}_$i (void)" |
| puts $f "{" |
| puts $f "}" |
| } |
| } |
| |
| proc _get_class_spec { spec name } { |
| foreach { key value } $spec { |
| if { $key == $name } { |
| return $value |
| } |
| } |
| switch $name { |
| count { |
| return $GenPerfTest::DEFAULT_CLASS_COUNT |
| } |
| nr_members { |
| return $GenPerfTest::DEFAULT_CLASS_NR_MEMBERS |
| } |
| nr_static_members { |
| return $GenPerfTest::DEFAULT_CLASS_NR_STATIC_MEMBERS |
| } |
| nr_methods { |
| return $GenPerfTest::DEFAULT_CLASS_NR_METHODS |
| } |
| nr_inline_methods { |
| return $GenPerfTest::DEFAULT_CLASS_NR_INLINE_METHODS |
| } |
| nr_static_methods { |
| return $GenPerfTest::DEFAULT_CLASS_NR_STATIC_METHODS |
| } |
| nr_static_inline_methods { |
| return $GenPerfTest::DEFAULT_CLASS_NR_STATIC_INLINE_METHODS |
| } |
| default { |
| error "required class-spec not present: $name" |
| } |
| } |
| } |
| |
| # SO_NR is the shlib number or "" for the binary. |
| # CU_NR is either the compilation unit number or "main". |
| # NAME is the "name" field from the class spec, which is |
| # { ns0 ns1 ... nsN class_name }. |
| # C is the iteration number, from the "count" field from the class spec. |
| |
| proc _make_class_name { so_nr cu_nr name c } { |
| set class_name [lindex $name [expr [llength $name] - 1]] |
| if { "$so_nr" != "" } { |
| set prefix "shlib${so_nr}_" |
| } else { |
| set prefix "" |
| } |
| return "${prefix}cu_${cu_nr}_${class_name}_${c}" |
| } |
| |
| proc _make_namespace_name { name } { |
| if { "$name" == "anonymous" } { |
| return "" |
| } |
| return $name |
| } |
| |
| proc _write_class_definitions { self_var f static run_nr so_nr cu_nr } { |
| upvar 1 $self_var self |
| set class_specs [_get_param $self(class_specs) $run_nr] |
| foreach spec $class_specs { |
| set count [_get_class_spec $spec count] |
| set name [_get_class_spec $spec name] |
| set nr_members [_get_class_spec $spec nr_members] |
| set nr_static_members [_get_class_spec $spec nr_static_members] |
| set nr_methods [_get_class_spec $spec nr_methods] |
| set nr_static_methods [_get_class_spec $spec nr_static_methods] |
| set depth [expr [llength $name] - 1] |
| for { set c 0 } { $c < $count } { incr c } { |
| puts $f "" |
| for { set i 0 } { $i < $depth } { incr i } { |
| puts $f "namespace [_make_namespace_name [lindex $name $i]]" |
| puts $f "\{" |
| puts $f "" |
| } |
| set class_name [_make_class_name $so_nr $cu_nr $name $c] |
| puts $f "class $class_name" |
| puts $f "\{" |
| puts $f " public:" |
| for { set i 0 } { $i < $nr_members } { incr i } { |
| puts $f " int member_$i;" |
| } |
| for { set i 0 } { $i < $nr_static_members } { incr i } { |
| # Rather than parameterize the number of const/non-const |
| # members, and their types, we keep it simple for now. |
| if { $i % 2 == 0 } { |
| puts $f " static const int static_member_$i = $i;" |
| } else { |
| puts $f " static int static_member_$i;" |
| } |
| } |
| for { set i 0 } { $i < $nr_methods } { incr i } { |
| puts $f " void method_$i (void);" |
| } |
| for { set i 0 } { $i < $nr_static_methods } { incr i } { |
| puts $f " static void static_method_$i (void);" |
| } |
| _write_inline_methods self $f $so_nr $cu_nr $spec $c |
| _write_static_inline_methods self $f $so_nr $cu_nr $spec $c |
| puts $f "\};" |
| for { set i [expr $depth - 1] } { $i >= 0 } { incr i -1 } { |
| puts $f "" |
| puts $f "\} // [lindex $name $i]" |
| } |
| } |
| } |
| } |
| |
| proc _write_inline_methods { self_var f so_nr cu_nr spec c } { |
| upvar 1 $self_var self |
| set name [_get_class_spec $spec name] |
| set nr_inline_methods [_get_class_spec $spec nr_inline_methods] |
| for { set i 0 } { $i < $nr_inline_methods } { incr i } { |
| puts $f " void inline_method_$i (void) { }" |
| } |
| } |
| |
| proc _write_static_inline_methods { self_var f so_nr cu_nr spec c } { |
| upvar 1 $self_var self |
| set name [_get_class_spec $spec name] |
| set nr_static_inline_methods [_get_class_spec $spec nr_static_inline_methods] |
| for { set i 0 } { $i < $nr_static_inline_methods } { incr i } { |
| puts $f " static void static_inline_method_$i (void) { }" |
| } |
| } |
| |
| proc _write_class_implementations { self_var f static run_nr so_nr cu_nr } { |
| upvar 1 $self_var self |
| set class_specs [_get_param $self(class_specs) $run_nr] |
| foreach spec $class_specs { |
| set count [_get_class_spec $spec count] |
| set name [_get_class_spec $spec name] |
| set depth [expr [llength $name] - 1] |
| for { set c 0 } { $c < $count } { incr c } { |
| for { set i 0 } { $i < $depth } { incr i } { |
| puts $f "" |
| puts $f "namespace [_make_namespace_name [lindex $name $i]]" |
| puts $f "\{" |
| } |
| _write_static_members self $f $so_nr $cu_nr $spec $c |
| _write_methods self $f $so_nr $cu_nr $spec $c |
| _write_static_methods self $f $so_nr $cu_nr $spec $c |
| for { set i [expr $depth - 1] } { $i >= 0 } { incr i -1 } { |
| puts $f "" |
| puts $f "\} // [lindex $name $i]" |
| } |
| } |
| } |
| } |
| |
| proc _write_static_members { self_var f so_nr cu_nr spec c } { |
| upvar 1 $self_var self |
| set name [_get_class_spec $spec name] |
| set nr_static_members [_get_class_spec $spec nr_static_members] |
| set class_name [_make_class_name $so_nr $cu_nr $name $c] |
| puts $f "" |
| # Rather than parameterize the number of const/non-const |
| # members, and their types, we keep it simple for now. |
| for { set i 0 } { $i < $nr_static_members } { incr i } { |
| if { $i % 2 == 0 } { |
| # Static const members are initialized inline. |
| } else { |
| puts $f "int ${class_name}::static_member_$i = $i;" |
| } |
| } |
| } |
| |
| proc _write_methods { self_var f so_nr cu_nr spec c } { |
| upvar 1 $self_var self |
| set name [_get_class_spec $spec name] |
| set nr_methods [_get_class_spec $spec nr_methods] |
| set class_name [_make_class_name $so_nr $cu_nr $name $c] |
| for { set i 0 } { $i < $nr_methods } { incr i } { |
| puts $f "" |
| puts $f "void" |
| puts $f "${class_name}::method_$i (void)" |
| puts $f "{" |
| puts $f "}" |
| } |
| } |
| |
| proc _write_static_methods { self_var f so_nr cu_nr spec c } { |
| upvar 1 $self_var self |
| set name [_get_class_spec $spec name] |
| set nr_static_methods [_get_class_spec $spec nr_static_methods] |
| set class_name [_make_class_name $so_nr $cu_nr $name $c] |
| for { set i 0 } { $i < $nr_static_methods } { incr i } { |
| puts $f "" |
| puts $f "void" |
| puts $f "${class_name}::static_method_$i (void)" |
| puts $f "{" |
| puts $f "}" |
| } |
| } |
| |
| proc _gen_compunit_header { self_var static run_nr so_nr cu_nr } { |
| upvar 1 $self_var self |
| set header_file [_make_header_name self $static $run_nr $so_nr $cu_nr] |
| set f [_create_file self $header_file] |
| _write_intro self $f |
| set header_macro [_make_header_macro "HEADER_CU" $cu_nr] |
| puts $f "" |
| puts $f "#ifndef $header_macro" |
| puts $f "#define $header_macro" |
| if [_classes_enabled_p self $run_nr] { |
| _write_class_definitions self $f $static $run_nr $so_nr $cu_nr |
| } |
| puts $f "" |
| puts $f "#endif // $header_macro" |
| close $f |
| return $header_file |
| } |
| |
| proc _gen_binary_compunit_source { self_var static run_nr cu_nr } { |
| upvar 1 $self_var self |
| set source_file [_make_source_name self $static $run_nr "" $cu_nr] |
| set f [_create_file self $source_file] |
| _write_intro self $f |
| _write_includes self $f [_get_param $self(binary_extra_headers) $run_nr] |
| set header_file [_make_header_basename self $static $run_nr "" $cu_nr] |
| puts $f "#include \"$header_file\"" |
| _write_static_globals self $f $run_nr |
| _write_extern_globals self $f $run_nr "" $cu_nr |
| _write_static_functions self $f $run_nr |
| _write_extern_functions self $f $run_nr "" $cu_nr |
| if [_classes_enabled_p self $run_nr] { |
| _write_class_implementations self $f $static $run_nr "" $cu_nr |
| } |
| close $f |
| return $source_file |
| } |
| |
| # Generate the sources for the pieces of the binary. |
| # The result is a list of source file names and accompanying object file |
| # names. The pieces are split across workers. |
| # E.g., with 10 workers the result for worker 0 is |
| # { { source0 header0 object0 } { source10 header10 object10 } ... } |
| |
| proc _gen_binary_source { self_var worker_nr static run_nr } { |
| upvar 1 $self_var self |
| verbose -log "GenPerfTest::_gen_binary_source worker $worker_nr run $run_nr, started [timestamp -format %c]" |
| set nr_compunits [_get_param $self(nr_compunits) $run_nr] |
| global PERF_TEST_COMPILE_PARALLELISM |
| set nr_workers $PERF_TEST_COMPILE_PARALLELISM |
| set result {} |
| for { set cu_nr $worker_nr } { $cu_nr < $nr_compunits } { incr cu_nr $nr_workers } { |
| set header_file [_gen_compunit_header self $static $run_nr "" $cu_nr] |
| set source_file [_gen_binary_compunit_source self $static $run_nr $cu_nr] |
| set object_file [_make_binary_object_name self $static $run_nr $cu_nr] |
| lappend result [list $source_file $header_file $object_file] |
| } |
| verbose -log "GenPerfTest::_gen_binary_source worker $worker_nr run $run_nr, done [timestamp -format %c]" |
| return $result |
| } |
| |
| proc _gen_shlib_compunit_source { self_var static run_nr so_nr cu_nr } { |
| upvar 1 $self_var self |
| set source_file [_make_source_name self $static $run_nr $so_nr $cu_nr] |
| set f [_create_file self $source_file] |
| _write_intro self $f |
| _write_includes self $f [_get_param $self(gen_shlib_extra_headers) $run_nr] |
| set header_file [_make_header_basename self $static $run_nr $so_nr $cu_nr] |
| puts $f "#include \"$header_file\"" |
| _write_static_globals self $f $run_nr |
| _write_extern_globals self $f $run_nr "shlib${so_nr}_" $cu_nr |
| _write_static_functions self $f $run_nr |
| _write_extern_functions self $f $run_nr "shlib${so_nr}_" $cu_nr |
| if [_classes_enabled_p self $run_nr] { |
| _write_class_implementations self $f $static $run_nr $so_nr $cu_nr |
| } |
| close $f |
| return $source_file |
| } |
| |
| # CU_NAME is a name from gen_shlib_extra_sources or tail_shlib_sources. |
| |
| proc _make_shlib_common_source_name { self_var static run_nr so_nr cu_name } { |
| upvar 1 $self_var self |
| if { !$static } { |
| set run_name [_get_param $self(run_names) $run_nr] |
| set source_name "${run_name}-lib${so_nr}-${cu_name}" |
| } else { |
| set source_name "lib${so_nr}-${cu_name}" |
| } |
| return "[_make_object_dir_name self $static $run_nr]/[_convert_spaces $source_name]" |
| } |
| |
| # N.B. gdb_compile_shlib doesn't support parallel builds of shlibs from |
| # common sources: the .o file path will be the same across all shlibs. |
| # gen_shlib_extra_sources may be common across all shlibs but they're each |
| # compiled with -DSHLIB=$SHLIB so we need different .o files for each |
| # shlib, and therefore we need different source files for each shlib. |
| # If this turns out to be too cumbersome we can augment gdb_compile_shlib. |
| |
| proc _gen_shlib_common_source { self_var static run_nr so_nr source_name } { |
| upvar 1 $self_var self |
| global srcdir |
| set source_file [_make_shlib_common_source_name self $static $run_nr $so_nr $source_name] |
| file copy -force "$srcdir/gdb.perf/$source_name" ${source_file} |
| return $source_file |
| } |
| |
| # Generate the sources for a shared library. |
| # The result is a list of source and header file names. |
| # E.g., { { source0 source1 ... common0 ... } { header0 header1 ... } } |
| |
| proc _gen_shlib_source { self_var static run_nr so_nr } { |
| upvar 1 $self_var self |
| verbose -log "GenPerfTest::_gen_shlib_source run $run_nr so $so_nr, started [timestamp -format %c]" |
| set headers {} |
| set sources {} |
| set nr_compunits [_get_param $self(nr_compunits) $run_nr] |
| for { set cu_nr 0 } { $cu_nr < $nr_compunits } { incr cu_nr } { |
| lappend headers [_gen_compunit_header self $static $run_nr $so_nr $cu_nr] |
| lappend sources [_gen_shlib_compunit_source self $static $run_nr $so_nr $cu_nr] |
| } |
| foreach source_name [_get_param $self(gen_shlib_extra_sources) $run_nr] { |
| lappend sources [_gen_shlib_common_source self $static $run_nr $so_nr $source_name] |
| } |
| verbose -log "GenPerfTest::_gen_shlib_source run $run_nr so $so_nr, done [timestamp -format %c]" |
| return [list $sources $headers] |
| } |
| |
| # Write Tcl array ARRAY_NAME to F. |
| |
| proc _write_tcl_array { self_var f array_name } { |
| upvar 1 $self_var self |
| if { "$array_name" != "$self_var" } { |
| global $array_name |
| } |
| puts $f "== $array_name ==" |
| foreach { name value } [array get $array_name] { |
| puts $f "$name: $value" |
| } |
| } |
| |
| # Write global Tcl state used for compilation to F. |
| # If anything changes we want to recompile. |
| |
| proc _write_tcl_state { self_var f dest } { |
| upvar 1 $self_var self |
| |
| # TODO(dje): gdb_default_target_compile references a lot of global |
| # state. Can we capture it all? For now these are the important ones. |
| |
| set vars { CC_FOR_TARGET CXX_FOR_TARGET CFLAGS_FOR_TARGET } |
| foreach v $vars { |
| global $v |
| if [info exists $v] { |
| eval set value $$v |
| puts $f "$v: $value" |
| } |
| } |
| |
| puts $f "" |
| _write_tcl_array self $f target_info |
| puts $f "" |
| _write_tcl_array self $f board_info |
| } |
| |
| # Write all sideband non-file inputs, as well as OPTIONS to INPUTS_FILE. |
| # If anything changes we want to recompile. |
| |
| proc _write_inputs_file { self_var dest inputs_file options } { |
| upvar 1 $self_var self |
| global env |
| set f [open $inputs_file "w"] |
| _write_tcl_array self $f self |
| puts $f "" |
| puts $f "options: $options" |
| puts $f "PATH: [getenv PATH]" |
| puts $f "" |
| _write_tcl_state self $f $dest |
| close $f |
| } |
| |
| # Generate the sha1sum of all the inputs. |
| # The result is a list of { error_code text }. |
| # Upon success error_code is zero and text is the sha1sum. |
| # Otherwise, error_code is non_zero and text is the error message. |
| |
| proc _gen_sha1sum_for_inputs { source_files header_files inputs } { |
| global srcdir subdir CAT_PROGRAM SHA1SUM_PROGRAM |
| set header_paths "" |
| foreach f $header_files { |
| switch -glob -- $f { |
| "<*>" { |
| # skip |
| } |
| "*gdb.perf/outputs/*" { |
| # in build tree |
| append header_paths " $f" |
| } |
| default { |
| append header_paths " $srcdir/$subdir/$f" |
| } |
| } |
| } |
| verbose -log "_gen_sha1sum_for_inputs: summing $source_files $header_paths $inputs" |
| set catch_result [catch "exec $CAT_PROGRAM $source_files $header_paths $inputs | $SHA1SUM_PROGRAM" output] |
| return [list $catch_result $output] |
| } |
| |
| # Return the contents of TEXT_FILE. |
| # It is assumed TEXT_FILE exists and is readable. |
| # This is used for reading files containing sha1sums, the |
| # last newline is removed. |
| |
| proc _read_file { text_file } { |
| set f [open $text_file "r"] |
| set result [read -nonewline $f] |
| close $f |
| return $result |
| } |
| |
| # Write TEXT to TEXT_FILE. |
| # It is assumed TEXT_FILE can be opened/created and written to. |
| |
| proc _write_file { text_file text } { |
| set f [open $text_file "w"] |
| puts $f $text |
| close $f |
| } |
| |
| # Wrapper on gdb_compile* that computes sha1sums of inputs to decide |
| # whether the compile is needed. |
| # The result is the result of gdb_compile*: "" == success, otherwise |
| # a compilation error occurred and the output is an error message. |
| # This doesn't take all inputs into account, just the useful ones. |
| # As an extension (or simplification) on gdb_compile*, if TYPE is |
| # shlib then call gdb_compile_shlib, otherwise call gdb_compile. |
| # Other possibilities *could* be handled this way, e.g., pthreads. TBD. |
| |
| proc _perftest_compile { self_var source_files header_files dest type options } { |
| upvar 1 $self_var self |
| verbose -log "_perftest_compile $source_files $header_files $dest $type $options" |
| # To keep things simple, we put all non-file inputs into a file and |
| # then cat all input files through sha1sum. |
| set sha1sum_file ${dest}.sha1sum |
| set sha1new_file ${dest}.sha1new |
| set inputs_file ${dest}.inputs |
| global srcdir subdir |
| set all_options $options |
| lappend all_options "incdir=$srcdir/$subdir" |
| _write_inputs_file self $dest $inputs_file $all_options |
| set sha1sum [_gen_sha1sum_for_inputs $source_files $header_files $inputs_file] |
| if { [lindex $sha1sum 0] != 0 } { |
| return "sha1sum generation error: [lindex $sha1sum 1]" |
| } |
| set sha1sum [lindex $sha1sum 1] |
| if ![file exists $dest] { |
| file delete $sha1sum_file |
| } |
| if [file exists $sha1sum_file] { |
| set last_sha1sum [_read_file $sha1sum_file] |
| verbose -log "last: $last_sha1sum, new: $sha1sum" |
| if { $sha1sum == $last_sha1sum } { |
| verbose -log "using existing build for $dest" |
| return "" |
| } |
| } |
| # No such luck, we need to compile. |
| file delete $sha1sum_file |
| if { $type == "shlib" } { |
| set result [gdb_compile_shlib $source_files $dest $all_options] |
| } else { |
| set result [gdb_compile $source_files $dest $type $all_options] |
| } |
| if { $result == "" } { |
| _write_file $sha1sum_file $sha1sum |
| verbose -log "wrote sha1sum: $sha1sum" |
| } |
| return $result |
| } |
| |
| proc _compile_binary_pieces { self_var worker_nr static run_nr } { |
| upvar 1 $self_var self |
| set compile_options [_compile_options self] |
| set nr_compunits [_get_param $self(nr_compunits) $run_nr] |
| set extra_headers [_get_param $self(binary_extra_headers) $run_nr] |
| global PERF_TEST_COMPILE_PARALLELISM |
| set nr_workers $PERF_TEST_COMPILE_PARALLELISM |
| # Generate the source first so we can more easily measure how long that |
| # takes. [It doesn't take hardly any time at all, relative to the time |
| # it takes to compile it, but this will provide numbers to show that.] |
| set todo_list [_gen_binary_source self $worker_nr $static $run_nr] |
| verbose -log "GenPerfTest::_compile_binary_pieces worker $worker_nr run $run_nr, started [timestamp -format %c]" |
| foreach elm $todo_list { |
| set source_file [lindex $elm 0] |
| set header_file [lindex $elm 1] |
| set object_file [lindex $elm 2] |
| set all_header_files $extra_headers |
| lappend all_header_files $header_file |
| set compile_result [_perftest_compile self $source_file $all_header_files $object_file object $compile_options] |
| if { $compile_result != "" } { |
| verbose -log "GenPerfTest::_compile_binary_pieces worker $worker_nr run $run_nr, failed [timestamp -format %c]" |
| verbose -log $compile_result |
| return -1 |
| } |
| } |
| verbose -log "GenPerfTest::_compile_binary_pieces worker $worker_nr run $run_nr, done [timestamp -format %c]" |
| return 0 |
| } |
| |
| # Helper function to compile the pieces of a shlib. |
| # Note: gdb_compile_shlib{,_pthreads} don't support first building object |
| # files and then building the shlib. Therefore our hands are tied, and we |
| # just build the shlib in one step. This is less of a parallelization |
| # problem if there are multiple shlibs: Each worker can build a different |
| # shlib. If this proves to be a problem in practice we can enhance |
| # gdb_compile_shlib* then. |
| |
| proc _compile_shlib { self_var static run_nr so_nr } { |
| upvar 1 $self_var self |
| set files [_gen_shlib_source self $static $run_nr $so_nr] |
| set source_files [lindex $files 0] |
| set header_files [lindex $files 1] |
| set extra_headers [_get_param $self(gen_shlib_extra_headers) $run_nr] |
| set shlib_file [_make_shlib_name self $static $run_nr $so_nr] |
| set compile_options "[_compile_options self] additional_flags=-DSHLIB=$so_nr" |
| set all_header_files $header_files |
| append all_header_files $extra_headers |
| set compile_result [_perftest_compile self $source_files $all_header_files $shlib_file shlib $compile_options] |
| if { $compile_result != "" } { |
| verbose -log "_compile_shlib failed: $compile_result" |
| return -1 |
| } |
| return 0 |
| } |
| |
| proc _gen_tail_shlib_source { self_var static run_nr } { |
| upvar 1 $self_var self |
| verbose -log "GenPerfTest::_gen_tail_shlib_source run $run_nr" |
| set source_files [_get_param $self(tail_shlib_sources) $run_nr] |
| if { [llength $source_files] == 0 } { |
| return "" |
| } |
| set result "" |
| foreach source_name $source_files { |
| lappend result [_gen_shlib_common_source self $static $run_nr tail $source_name] |
| } |
| return $result |
| } |
| |
| proc _make_tail_shlib_name { self_var static run_nr } { |
| upvar 1 $self_var self |
| set source_files [_get_param $self(tail_shlib_sources) $run_nr] |
| if { [llength $source_files] == 0 } { |
| return "" |
| } |
| return [_make_shlib_name self $static $run_nr "tail"] |
| } |
| |
| # Helper function to compile the tail shlib, if it's specified. |
| |
| proc _compile_tail_shlib { self_var static run_nr } { |
| upvar 1 $self_var self |
| set source_files [_gen_tail_shlib_source self $static $run_nr] |
| if { [llength $source_files] == 0 } { |
| return 0 |
| } |
| set header_files [_get_param $self(tail_shlib_headers) $run_nr] |
| set shlib_file [_make_tail_shlib_name self $static $run_nr] |
| set compile_options [_compile_options self] |
| set compile_result [_perftest_compile self $source_files $header_files $shlib_file shlib $compile_options] |
| if { $compile_result != "" } { |
| verbose -log "_compile_tail_shlib failed: $compile_result" |
| return -1 |
| } |
| verbose -log "_compile_tail_shlib failed: succeeded" |
| return 0 |
| } |
| |
| # Compile the pieces of the binary and possible shlibs for the test. |
| # The result is 0 for success, -1 for failure. |
| |
| proc _compile_pieces { self_var worker_nr } { |
| upvar 1 $self_var self |
| global PERF_TEST_COMPILE_PARALLELISM |
| set nr_workers $PERF_TEST_COMPILE_PARALLELISM |
| set nr_runs [llength $self(run_names)] |
| set static [_static_object_files_p self] |
| verbose -log "_compile_pieces: static flag: $static" |
| file mkdir "[file dirname $self(binfile)]/pieces" |
| if $static { |
| # All the generated pieces look the same (run over run) so just |
| # build all the shlibs of the last run (which is the largest). |
| set last_run [expr $nr_runs - 1] |
| set nr_gen_shlibs [_get_param $self(nr_gen_shlibs) $last_run] |
| set object_dir [_make_object_dir_name self $static ignored] |
| file mkdir $object_dir |
| for { set so_nr $worker_nr } { $so_nr < $nr_gen_shlibs } { incr so_nr $nr_workers } { |
| if { [_compile_shlib self $static $last_run $so_nr] < 0 } { |
| return -1 |
| } |
| } |
| # We don't shard building of tail-shlib, so only build it once. |
| if { $worker_nr == 0 } { |
| if { [_compile_tail_shlib self $static $last_run] < 0 } { |
| return -1 |
| } |
| } |
| if { [_compile_binary_pieces self $worker_nr $static $last_run] < 0 } { |
| return -1 |
| } |
| } else { |
| for { set run_nr 0 } { $run_nr < $nr_runs } { incr run_nr } { |
| set nr_gen_shlibs [_get_param $self(nr_gen_shlibs) $run_nr] |
| set object_dir [_make_object_dir_name self $static $run_nr] |
| file mkdir $object_dir |
| for { set so_nr $worker_nr } { $so_nr < $nr_gen_shlibs } { incr so_nr $nr_workers } { |
| if { [_compile_shlib self $static $run_nr $so_nr] < 0 } { |
| return -1 |
| } |
| } |
| # We don't shard building of tail-shlib, so only build it once. |
| if { $worker_nr == 0 } { |
| if { [_compile_tail_shlib self $static $run_nr] < 0 } { |
| return -1 |
| } |
| } |
| if { [_compile_binary_pieces self $worker_nr $static $run_nr] < 0 } { |
| return -1 |
| } |
| } |
| } |
| return 0 |
| } |
| |
| # Main function invoked by each worker. |
| # This builds all the things that are possible to build in parallel, |
| # sharded up among all the workers. |
| |
| proc compile_pieces { self_var worker_nr } { |
| upvar 1 $self_var self |
| verbose -log "GenPerfTest::compile_pieces worker $worker_nr, started [timestamp -format %c]" |
| verbose -log "self: [array get self]" |
| _verify_testcase self |
| if { [_compile_pieces self $worker_nr] < 0 } { |
| verbose -log "GenPerfTest::compile_pieces worker $worker_nr, failed [timestamp -format %c]" |
| return -1 |
| } |
| verbose -log "GenPerfTest::compile_pieces worker $worker_nr, done [timestamp -format %c]" |
| return 0 |
| } |
| |
| proc _make_shlib_options { self_var static run_nr } { |
| upvar 1 $self_var self |
| set nr_gen_shlibs [_get_param $self(nr_gen_shlibs) $run_nr] |
| set result "" |
| for { set i 0 } { $i < $nr_gen_shlibs } { incr i } { |
| lappend result "shlib=[_make_shlib_name self $static $run_nr $i]" |
| } |
| set tail_shlib_name [_make_tail_shlib_name self $static $run_nr] |
| if { "$tail_shlib_name" != "" } { |
| lappend result "shlib=$tail_shlib_name" |
| } |
| return $result |
| } |
| |
| proc _compile_binary { self_var static run_nr } { |
| upvar 1 $self_var self |
| set input_files [_make_binary_input_file_names self $static $run_nr] |
| set extra_headers [_get_param $self(binary_extra_headers) $run_nr] |
| set binary_file [_make_binary_name self $run_nr] |
| set compile_options [_compile_options self] |
| set shlib_options [_make_shlib_options self $static $run_nr] |
| if { [llength $shlib_options] > 0 } { |
| append compile_options " " $shlib_options |
| } |
| set compile_result [_perftest_compile self $input_files $extra_headers $binary_file executable $compile_options] |
| if { $compile_result != "" } { |
| verbose -log "_compile_binary failed: $compile_result" |
| return -1 |
| } |
| return 0 |
| } |
| |
| # Helper function for compile. |
| # The result is 0 for success, -1 for failure. |
| |
| proc _compile { self_var } { |
| upvar 1 $self_var self |
| set nr_runs [llength $self(run_names)] |
| set static [_static_object_files_p self] |
| verbose -log "_compile: static flag: $static" |
| for { set run_nr 0 } { $run_nr < $nr_runs } { incr run_nr } { |
| if { [_compile_binary self $static $run_nr] < 0 } { |
| return -1 |
| } |
| } |
| return 0 |
| } |
| |
| # Main function to compile the test program. |
| # It is assumed all the pieces of the binary (all the .o's, except those |
| # from test-supplied sources) have already been built with compile_pieces. |
| # There's no need to compile any shlibs here, as compile_pieces will have |
| # already built them too. |
| # The result is 0 for success, -1 for failure. |
| |
| proc compile { self_var } { |
| upvar 1 $self_var self |
| verbose -log "GenPerfTest::compile, started [timestamp -format %c]" |
| verbose -log "self: [array get self]" |
| _verify_testcase self |
| if { [_compile self] < 0 } { |
| verbose -log "GenPerfTest::compile, failed [timestamp -format %c]" |
| return -1 |
| } |
| verbose -log "GenPerfTest::compile, done [timestamp -format %c]" |
| return 0 |
| } |
| |
| # Main function for running a test. |
| # It is assumed that the test program has already been built. |
| |
| proc run { builder_exp_file_name make_config_thunk_name py_file_name test_class_name } { |
| verbose -log "GenPerfTest::run, started [timestamp -format %c]" |
| verbose -log "GenPerfTest::run, $builder_exp_file_name $make_config_thunk_name $py_file_name $test_class_name" |
| |
| set testprog [file rootname $builder_exp_file_name] |
| |
| # This variable is required by perftest.exp. |
| # This isn't the name of the test program, it's the name of the .py |
| # test. The harness assumes they are the same, which is not the case |
| # here. |
| global testfile |
| set testfile [file rootname $py_file_name] |
| |
| GenPerfTest::load_test_description $builder_exp_file_name |
| |
| array set testcase [$make_config_thunk_name] |
| |
| PerfTest::assemble { |
| # Compilation is handled elsewhere. |
| return 0 |
| } { |
| clean_restart |
| return 0 |
| } { |
| global gdb_prompt |
| gdb_test_multiple "python ${test_class_name}('$testprog:$testfile', [tcl_string_list_to_python_list $testcase(run_names)], '$testcase(binfile)').run()" "run test" { |
| -re "Error while executing Python code.\[\r\n\]+$gdb_prompt $" { |
| return -1 |
| } |
| -re "\[\r\n\]+$gdb_prompt $" { |
| } |
| } |
| return 0 |
| } |
| verbose -log "GenPerfTest::run, done [timestamp -format %c]" |
| return 0 |
| } |
| |
| # This function is invoked by the testcase builder scripts |
| # (e.g., gmonster[12].exp). |
| # It is not invoked by the testcase runner scripts |
| # (e.g., gmonster[12]-*.exp). |
| |
| proc standard_compile_driver { exp_file_name make_config_thunk_name } { |
| global GDB_PERFTEST_MODE GDB_PERFTEST_SUBMODE |
| if ![info exists GDB_PERFTEST_SUBMODE] { |
| # Probably a plain "make check-perf", nothing to do. |
| # Give the user a reason why we're not running this test. |
| verbose -log "Test must be compiled/run in separate steps." |
| return 0 |
| } |
| switch -glob -- "$GDB_PERFTEST_MODE/$GDB_PERFTEST_SUBMODE" { |
| compile/gen-workers { |
| if { [GenPerfTest::gen_worker_files $exp_file_name] < 0 } { |
| fail $GDB_PERFTEST_MODE |
| return -1 |
| } |
| pass $GDB_PERFTEST_MODE |
| } |
| compile/build-pieces { |
| array set testcase [$make_config_thunk_name] |
| global PROGRAM_NAME WORKER_NR |
| if { [GenPerfTest::compile_pieces testcase $WORKER_NR] < 0 } { |
| fail $GDB_PERFTEST_MODE |
| # This gdb.log lives in a different place, help the user |
| # find it. |
| set output_dir "gdb.perf/outputs" |
| send_user "check ${output_dir}/${PROGRAM_NAME}/${PROGRAM_NAME}-${WORKER_NR}/gdb.log\n" |
| return -1 |
| } |
| pass $GDB_PERFTEST_MODE |
| } |
| compile/final { |
| array set testcase [$make_config_thunk_name] |
| if { [GenPerfTest::compile testcase] < 0 } { |
| fail $GDB_PERFTEST_MODE |
| return -1 |
| } |
| pass $GDB_PERFTEST_MODE |
| } |
| run/* - both/* { |
| # Since the builder script is a .exp file living in gdb.perf |
| # we can get here (dejagnu will find this file for a default |
| # "make check-perf"). We can also get here when |
| # standard_run_driver loads the builder .exp file. |
| } |
| default { |
| error "Bad value for GDB_PERFTEST_MODE/GDB_PERFTEST_SUBMODE: $GDB_PERFTEST_MODE/$GDB_PERFTEST_SUBMODE" |
| } |
| } |
| return 0 |
| } |
| |
| # This function is invoked by the testcase runner scripts |
| # (e.g., gmonster[12]-*.exp). |
| # It is not invoked by the testcase builder scripts |
| # (e.g., gmonster[12].exp). |
| # |
| # These tests are built separately with |
| # "make build-perf" and run with |
| # "make check-perf GDB_PERFTEST_MODE=run". |
| # Eventually we can support GDB_PERFTEST_MODE=both, but for now we don't. |
| |
| proc standard_run_driver { builder_exp_file_name make_config_thunk_name py_file_name test_class_name } { |
| global GDB_PERFTEST_MODE |
| # First step is to compile the test. |
| switch $GDB_PERFTEST_MODE { |
| compile - both { |
| # Here is where we'd add code to support a plain |
| # "make check-perf". |
| } |
| run { |
| } |
| default { |
| error "Bad value for GDB_PERFTEST_MODE: $GDB_PERFTEST_MODE" |
| } |
| } |
| # Now run the test. |
| switch $GDB_PERFTEST_MODE { |
| compile { |
| } |
| both { |
| # Give the user a reason why we're not running this test. |
| verbose -log "Test must be compiled/run in separate steps." |
| } |
| run { |
| if { [GenPerfTest::run $builder_exp_file_name $make_config_thunk_name $py_file_name $test_class_name] < 0 } { |
| fail $GDB_PERFTEST_MODE |
| return -1 |
| } |
| pass $GDB_PERFTEST_MODE |
| } |
| } |
| return 0 |
| } |
| } |
| |
| if ![info exists PERF_TEST_COMPILE_PARALLELISM] { |
| set PERF_TEST_COMPILE_PARALLELISM $GenPerfTest::DEFAULT_PERF_TEST_COMPILE_PARALLELISM |
| } |