1510 lines
51 KiB
Text
1510 lines
51 KiB
Text
|
# 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
|
||
|
}
|