# Copyright (C) 2013-2021 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 . # # 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__{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 }