Add perf testcase generator.
authorDoug Evans <dje@google.com>
Fri, 24 Jul 2015 22:43:15 +0000 (15:43 -0700)
committerDoug Evans <dje@google.com>
Fri, 24 Jul 2015 22:43:15 +0000 (15:43 -0700)
gdb/testsuite/ChangeLog:

* gdb.perf/README: New file.
* lib/perftest.exp (tcl_string_list_to_python_list): New function.
* lib/gen-perf-test.exp: New file.

gdb/testsuite/ChangeLog
gdb/testsuite/gdb.perf/README [new file with mode: 0644]
gdb/testsuite/lib/gen-perf-test.exp [new file with mode: 0644]
gdb/testsuite/lib/perftest.exp

index 1c65cc2b820ecbf2e3d8fae8625cc4fd22320285..5371caeb09e5daa9880e4a938d04587a3c86a367 100644 (file)
@@ -1,3 +1,9 @@
+2015-07-24  Doug Evans  <dje@google.com>
+
+       * gdb.perf/README: New file.
+       * lib/perftest.exp (tcl_string_list_to_python_list): New function.
+       * lib/gen-perf-test.exp: New file.
+
 2015-07-24  Doug Evans  <dje@google.com>
 
        * lib/perftest.exp (PerfTest::compile): Unconditionally call body.
diff --git a/gdb/testsuite/gdb.perf/README b/gdb/testsuite/gdb.perf/README
new file mode 100644 (file)
index 0000000..ee0a648
--- /dev/null
@@ -0,0 +1,211 @@
+The GDB Performance Testsuite
+=============================
+
+This README contains notes on hacking on GDB's performance testsuite.
+For notes on GDB's regular testsuite or how to run the performance testsuite,
+see ../README.
+
+Generated tests
+***************
+
+The testcase generator lets us easily test GDB on large programs.
+The "monster" tests are mocks of real programs where GDB's
+performance has been a problem.  Often it is difficult to build
+these monster programs, but when measuring performance one doesn't
+need the "real" program, all one needs is something that looks like
+the real program along the axis one is measuring; for example, the
+number of CUs (compilation units).
+
+Structure of generated tests
+****************************
+
+Generated tests consist of a binary and potentially any number of
+shared libraries.  One of these shared libraries, called "tail", is
+special.  It is used to provide mocks of system provided code, and
+contains no generated code.  Typically system-provided libraries
+are searched last which can have significant performance consequences,
+so we provide a means to exercise that.
+
+The binary and the generated shared libraries can have a mix of
+manually written and generated code.  Manually written code is
+specified with the {binary,gen_shlib}_extra_sources config parameters,
+which are lists of source files in testsuite/gdb.perf.  Generated
+files are controlled with various configuration knobs.
+
+Once a large test program is built, it makes sense to use it as much
+as possible (i.e., with multiple tests).  Therefore perf data collection
+for generated tests is split into two passes: the first pass builds
+all the generated tests, and the second pass runs all the performance
+tests.  The first pass is called "build-perf" and the second pass is
+called "check-perf".  See ../README for instructions on running the tests.
+
+Generated test directory layout
+*******************************
+
+All output lives under testsuite/gdb.perf in the build directory.
+
+Because some of the tests can get really large (and take potentially
+minutes to compile), parallelism is built into their compilation.
+Note however that we don't run the tests in parallel as it can skew
+the results.
+
+To keep things simple and stay consistent, we use the same
+mechanism used by "make check-parallel".  There is one catch: we need
+one .exp for each "worker" but the .exp file must come from the source
+tree.  To avoid generating .exp files for each worker we invoke
+lib/build-piece.exp for each worker with different arguments.
+The file build.piece.exp lives in "lib" to prevent dejagnu from finding
+it when it goes to look for .exp scripts to run.
+
+Another catch is that each parallel build worker needs its own directory
+so that their gdb.{log,sum} files don't collide.  On the other hand
+its easier if their output (all the object files and shared libraries)
+are in the same directory.
+
+The above considerations yield the resulting layout:
+
+$objdir/testsuite/gdb.perf/
+
+       gdb.log, gdb.sum: result of doing final link and running tests
+
+       workers/
+
+               gdb.log, gdb.sum: result of gen-workers step
+
+               $program_name/
+
+                       ${program_name}-0.worker
+                       ...
+                       ${program_name}-N.worker: input to build-pieces step
+
+       outputs/
+
+               ${program_name}/
+
+                       ${program_name}-0/
+                       ...
+                       ${program_name}-N/
+
+                               gdb.log, gdb.sum: for each build-piece worker
+
+                       pieces/
+
+                               generated sources, object files, shlibs
+
+                       ${run_name_1}: binary for test config #1
+                       ...
+                       ${run_name_N}: binary for test config #N
+
+Generated test configuration knobs
+**********************************
+
+The monster program generator provides various knobs for building various
+kinds of monster programs.  For a list of the knobs see function
+GenPerfTest::init_testcase in testsuite/lib/perftest.exp.
+Most knobs are self-explanatory.
+Here is a description of the less obvious ones.
+
+binary_extra_sources
+
+       This is the list of non-machine generated sources that go
+       into the test binary.  There must be at least one: the one
+       with main.
+
+class_specs
+
+       List of pairs of keys and values.
+       Supported keys are:
+       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 bar {}; }
+       would be represented as:
+       {
+         { count 1 name { foo } }
+         { count 1 name { ns1 bar } }
+       }
+
+       The naming of each class is "class_<cu_nr>_<class_nr>",
+       where <cu_nr> is the number of the compilation unit the
+       class is defined in.
+
+       There's currently no support for nesting classes in classes,
+       or for specifying baseclasses or templates.
+
+Misc. configuration knobs
+*************************
+
+These knobs control building or running of the test and are specified
+like any global Tcl variable.
+
+CAT_PROGRAM
+
+       Default is /bin/cat, you shouldn't need to change this.
+
+SHA1SUM_PROGRAM
+
+       Default is /usr/bin/sha1sum.
+
+PERF_TEST_COMPILE_PARALLELISM
+
+       An integer, specifies the amount of parallelism in the builds.
+       Akin to make's -j flag.  The default is 10.
+
+Writing a generated test program
+********************************
+
+The best way to write a generated test program is to take an existing
+one as boilerplate.  Two good examples are gmonster1.exp and gmonster2.exp.
+gmonster1.exp builds a big binary with various custom manually written
+code, and gmonster2 is (essentially) the equivalent binary split up over
+several shared libraries.
+
+Writing a performance test that uses a generated program
+********************************************************
+
+The best way to write a test is to take an existing one as boilerplate.
+Good examples are gmonster1-*.exp and gmonster2-*.exp.
+
+The naming used thus far is that "foo.exp" builds the test program
+and there is one "foo-bar.exp" file for each performance test
+that uses test program "foo".
+
+In addition to writing the test driver .exp script, one must also
+write a python script that is used to run the test.
+This contents of this script is defined by the performance testsuite
+harness.  It defines a class, which is a subclass of one of the
+classes in gdb.perf/lib/perftest/perftest.py.
+See gmonster-null-lookup.py for an example.
+
+Note: Since gmonster1 and gmonster2 are treated as being variations of
+the same program, each test shares the same python script.
+E.g., gmonster1-null-lookup.exp and gmonster2-null-lookup.exp
+both use gmonster-null-lookup.py.
+
+Running performance tests for generated programs
+************************************************
+
+There are two steps: build and run.
+
+Example:
+
+bash$ make -j10 build-perf RUNTESTFLAGS="gmonster1.exp"
+bash$ make -j10 check-perf RUNTESTFLAGS="gmonster1-null-lookup.exp" \
+    GDB_PERFTEST_MODE=run
diff --git a/gdb/testsuite/lib/gen-perf-test.exp b/gdb/testsuite/lib/gen-perf-test.exp
new file mode 100644 (file)
index 0000000..8da9fcf
--- /dev/null
@@ -0,0 +1,1509 @@
+# Copyright (C) 2013-2015 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
+}
index e5398e3cca9a3e1fcf161652877e9188c303156f..42cdbcf5fa717d7b9c506665e39e56fc40a3498c 100644 (file)
@@ -141,3 +141,17 @@ proc skip_perf_tests { } {
 
     return 1
 }
+
+# Given a list of tcl strings, return the same list as the text form of a
+# python list.
+
+proc tcl_string_list_to_python_list { l } {
+    proc quote { text } {
+       return "\"$text\""
+    }
+    set quoted_list ""
+    foreach elm $l {
+       lappend quoted_list [quote $elm]
+    }
+    return "([join $quoted_list {, }])"
+}