#!/bin/bash
# restart using a Tcl shell \
    exec tclsh $0 $@

# =====================================================================
#
#     ecosxda
#
# =====================================================================
# ####ECOSHOSTGPLCOPYRIGHTBEGIN####                                         
# -------------------------------------------                               
# This file is part of the eCos host tools.                                 
# Copyright (C) 2003, 2005 Free Software Foundation, Inc.                   
# Copyright (C) 2003, 2005 eCosCentric Limited                              
#
# 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 2 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, write to the                             
# Free Software Foundation, Inc., 51 Franklin Street,                       
# Fifth Floor, Boston, MA  02110-1301, USA.                                 
# -------------------------------------------                               
# ####ECOSHOSTGPLCOPYRIGHTEND####                                           
# =====================================================================
######DESCRIPTIONBEGIN####
#
# Author(s):    bartv
# Date:         2003-09-01
# Purpose:      convert an ecosgcov.out file into the individual .da
#               files needed by gcov
#
#####DESCRIPTIONEND####
# =====================================================================

# ----------------------------------------------------------------------------
# Globals

# The name of this program, for diagnostic messages. If this file is
# run directly it is defined here. If it has been sourced by another
# script then the variable should be defined already
if { ! [info exists ::program] } {
    set ::program       "ecosxda"
}

if { ! [info exists ::verbose] } {
    set ::verbose       0
}

if { ! [info exists ::version] } {
    set ::version       "1.1"
}

namespace eval ecosxda {

    # Utility
    proc verbose { level msg } {
        if { $::verbose >= $level } {
            puts $msg
        }
    }

    # Format information. This is held in the header of an ecosgcov.out file.
    variable    gcc_version     0
    variable    big_endian      0
    variable    sizeof_int      4
    variable    sizeof_ptr      4
    variable    longlong_byte0  0
    variable    gcov_magic      0
    variable    program_crc     0

    # ----------------------------------------------------------------------------
    # 64 bit arithmetic support. Some versions of Tcl support this automatically
    # but it cannot yet be assumed. gcov_types are held as eight separate bytes,
    # most significant first. This is not going to give high performance, but
    # good enough for now.

    variable    gcov_type_zero  [list 0 0 0 0 0 0 0 0]

    proc gcov_type_eq { a b } {
        for { set i 0 } { $i < 8 } { incr i } {
            if { [lindex $a $i] != [lindex $b $i] } {
                return 0
            }
        }
        return 1
    }
    
    proc gcov_type_max { a b } {
        for { set i 0 } { $i < 8 } { incr i } {
            set ai [lindex $a $i]
            set bi [lindex $b $i]
            if { $ai > $bi } {
                return $a
            } elseif { $ai < $bi } {
                return $b
            }
        }
        return $a
    }

    proc gcov_type_sum { a b } {
        set byte0       [expr [lindex $a 7] + [lindex $b 7]]
        set byte1       [expr [lindex $a 6] + [lindex $b 6]]
        set byte2       [expr [lindex $a 5] + [lindex $b 5]]
        set byte3       [expr [lindex $a 4] + [lindex $b 4]]
        set byte4       [expr [lindex $a 3] + [lindex $b 3]]
        set byte5       [expr [lindex $a 2] + [lindex $b 2]]
        set byte6       [expr [lindex $a 1] + [lindex $b 1]]
        set byte7       [expr [lindex $a 0] + [lindex $b 0]]
        if { $byte0 > 255 } {
            incr byte0 -256
            incr byte1 1
        }
        if { $byte1 > 255 } {
            incr byte1 -256
            incr byte2 1
        }
        if { $byte2 > 255 } {
            incr byte2 -256
            incr byte3 1
        }
        if { $byte3 > 255 } {
            incr byte3 -256
            incr byte4 1
        }
        if { $byte4 > 255 } {
            incr byte4 -256
            incr byte5 1
        }
        if { $byte5 > 255 } {
            incr byte5 -256
            incr byte6 1
        }
        if { $byte6 > 255 } {
            incr byte6 -256
            incr byte7 1
        }
        return [list $byte7 $byte6 $byte5 $byte4 $byte3 $byte2 $byte1 $byte0]
    }
    
    # ----------------------------------------------------------------------------
    # Support for reading and more importantly decoding a file. This
    # can be an ecosgcov.out file, a .da file for merging when the gcc
    # version is < 3.4, or a .gcda file for later gcc versions. The
    # file sizes typically depend on the number of basic blocks in the
    # application with one cyg_uint64 per basic block and typical
    # basic blocks rather larger than eight bytes. Hence the file
    # sizes should be rather less than the executable size, and there
    # should be no problem reading it in one go and processing it
    # entirely in memory.
    variable    input_filename  ""
    variable    input_raw       ""
    variable    input_len       0
    # The same data converted to a list of signed chars. This increases
    # the memory requirement significantly, but makes it easier to
    # perform some of the conversions.
    variable    input_list      [list]
    # Is the input in ecosgcov.out format or in .da/.gcda format?
    variable    input_format_ecosgcov 0

    # The current index for reading
    variable    input_index

    proc input_read { name ecosgcov_format } {
        verbose 1 "Reading file $name"
        
        # Higher-level code should have checked that the file exists and is readable
        if { [catch { open $name "r" } fd ] } {
            puts stderr "$::program : failed to read file $name"
            puts stderr "  $fd"
            return 0
        }
        fconfigure $fd -translation binary
        set ecosxda::input_raw [read $fd]
        close $fd

        set     ecosxda::input_filename $name
        set     ecosxda::input_len      [string length $ecosxda::input_raw]
        set     ecosxda::input_list     [list]
        binary scan $ecosxda::input_raw "c*" tmp
        foreach byte $tmp {
            lappend ecosxda::input_list [expr $byte & 0x00FF]
        }
        set     ecosxda::input_index    0
	set     ecosxda::input_format_ecosgcov $ecosgcov_format
        verbose 1 "  read $ecosxda::input_len bytes."
        return  1
    }

    proc input_reset { } {
        set     ecosxda::input_filename ""
        set     ecosxda::input_raw      ""
        set     ecosxda::input_len      0
        set     ecosxda::input_list     [list]
        set     ecosxda::input_index    0
    }

    proc input_done { } {
        return [expr ($ecosxda::input_index >= $ecosxda::input_len) ? 1 : 0]
    }

    proc input_ensure { count msg fatal } {
        if { ($ecosxda::input_index + $count) > $ecosxda::input_len } {
            puts stderr "$::program : invalid file $ecosxda::input_filename"
            puts stderr "    Expecting $count bytes for $msg at offset $ecosxda::input_index"
            puts stderr "    but file is only $ecosxda::input_len bytes."
            if { $fatal } {
                exit 1
            } else {
                return 0
            }
        } else {
            return 1
        }
    }
    
    # These return lists of items, extracting from input_list and
    # adjusting input_index
    proc input_bytes { count } {
        variable        input_index
        variable        input_len
        variable        input_list
        
        set result [lrange $input_list $ecosxda::input_index [expr $input_index + $count - 1]]
        incr input_index $count
        return $result
    }

    proc input_ints { count } {
        variable        input_index
        variable        input_len
        variable        input_list
	variable        input_format_ecosgcov
        variable        big_endian
        variable        gcc_version

        # FIXME: cope with 4 != sizeof(int)
        set result      [list]
        for { set i 0 } { $i < $count } { incr i } {
            set byte0   [lindex $input_list $input_index]
            set byte1   [lindex $input_list [expr $input_index + 1]]
            set byte2   [lindex $input_list [expr $input_index + 2]]
            set byte3   [lindex $input_list [expr $input_index + 3]]
            incr ecosxda::input_index 4

	    if { !$input_format_ecosgcov && ($gcc_version < 30400) } {
		# A .da file for gcc < 3.4. Always little-endian
		set int [expr ($byte3 << 24) + ($byte2 << 16) + ($byte1 << 8) + $byte0]
	    } elseif { $big_endian } {
		set int [expr ($byte0 << 24) + ($byte1 << 16) + ($byte2 << 8) + $byte3]
	    } else {
		set int [expr ($byte3 << 24) + ($byte2 << 16) + ($byte1 << 8) + $byte0]
	    } 
            lappend result $int
        }
        return $result
    }
    
    proc input_int { } {
        set result [lindex [input_ints 1] 0]
        return $result
    }
    
    proc input_ptrs { count } {
        variable        input_index
        variable        input_len
        variable        input_list
	variable        input_format_ecosgcov
        variable        big_endian
        variable        gcc_version
        
        # FIXME: cope with 4 != sizeof(void*)
        set result      [list]
        for { set i 0 } { $i < $count } { incr i } {
            set byte0   [lindex $input_list $input_index]
            set byte1   [lindex $input_list [expr $input_index + 1]]
            set byte2   [lindex $input_list [expr $input_index + 2]]
            set byte3   [lindex $input_list [expr $input_index + 3]]
            incr input_index 4

	    if { !$input_format_ecosgcov && ($gcc_version < 30400) } {
		# A .da file for gcc < 3.4. Always little-endian
		set int [expr ($byte3 << 24) + ($byte2 << 16) + ($byte1 << 8) + $byte0]
	    } elseif { $big_endian } {
		set int [expr ($byte0 << 24) + ($byte1 << 16) + ($byte2 << 8) + $byte3]
	    } else {
		set int [expr ($byte3 << 24) + ($byte2 << 16) + ($byte1 << 8) + $byte0]
	    } 
            lappend result $int
        }
        return $result
    }
    
    proc input_ptr { } {
        set result [lindex [input_ptrs 1] 0]
        return $result
    }
    
    # 64-bit arithmetic is available on recent versions of Tcl but cannot
    # yet be assumed. Instead each 64-bit value is held as a list of the
    # eight bytes, most-significant first.
    proc input_gcov_types { count } {
        variable        input_index
        variable        input_len
        variable        input_list
	variable        input_format_ecosgcov
        variable        longlong_byte0
        variable        big_endian
        variable        gcc_version
        
        set result      [list]
        for { set i 0 } { $i < $count } { incr i } {
            set byte0   [lindex $input_list $input_index]
            set byte1   [lindex $input_list [expr $input_index + 1]]
            set byte2   [lindex $input_list [expr $input_index + 2]]
            set byte3   [lindex $input_list [expr $input_index + 3]]
            set byte4   [lindex $input_list [expr $input_index + 4]]
            set byte5   [lindex $input_list [expr $input_index + 5]]
            set byte6   [lindex $input_list [expr $input_index + 6]]
            set byte7   [lindex $input_list [expr $input_index + 7]]
            incr input_index 8

	    if { $input_format_ecosgcov } {
		# The input data is 0x1122334455667788LL.
		if { 0x0011 == $longlong_byte0 } {
		    lappend result [list $byte0 $byte1 $byte2 $byte3 $byte4 $byte5 $byte6 $byte7]
		} elseif { 0x0044 == $longlong_byte0 } {
		    lappend result [list $byte3 $byte2 $byte1 $byte0 $byte7 $byte6 $byte5 $byte4]
		} elseif { 0x0055 == $longlong_byte0 } {
		    lappend result [list $byte4 $byte5 $byte6 $byte7 $byte0 $byte1 $byte2 $byte3]
		} else {
		    lappend result [list $byte7 $byte6 $byte5 $byte4 $byte3 $byte2 $byte1 $byte0]
		}
	    } elseif { $gcc_version < 30400 } {
		# The .da file always has the counts in little-endian format
		lappend result [list $byte7 $byte6 $byte5 $byte4 $byte3 $byte2 $byte1 $byte0]
	    } else {
		# Least significant integer first, then most
		# significant, using target-side endianness.
		if { $big_endian } {
		    lappend result [list $byte4 $byte5 $byte6 $byte7 $byte0 $byte1 $byte2 $byte3]
		} else {
		    lappend result [list $byte7 $byte6 $byte5 $byte4 $byte3 $byte2 $byte1 $byte0]
		}
	    }
        }
        return $result
    }

    proc input_gcov_type { } {
        set result [lindex [input_gcov_types 1] 0]
        return $result
    }
    
    # This returns a single string.
    proc input_string { } {
        variable        input_index
        variable        input_len
        variable        input_list
        variable        input_raw

        set end $input_index
        while { ($end < $input_len) && (0 != [lindex $input_list $end]) } {
            incr end
        }
        if { $end >= $input_len } {
            return ""
        }
        set result [string range $input_raw $ecosxda::input_index [expr $end - 1]]
        set len [expr ([string length $result] + 4) & ~3]
        incr input_index $len
        return $result
    }

    # ----------------------------------------------------------------------------
    # Support for outputting a file. This time the data is accumulated
    # in memory and written in a flush operation. The output
    # endianness should correspond to the target. 
    variable    output_list     [list]

    proc output_write { name } {
        set data [binary format "c*" $ecosxda::output_list]
        if { [catch {open $name "w"} fd] } {
            puts stderr "$::program : unable to write to $name"
            puts stderr "  $fd"
            return 0
        }
        fconfigure $fd -translation binary
        puts -nonewline $fd $data
        close $fd
        return 1
    }

    proc output_reset { } {
        set ecosxda::output_list        [list]
    }

    proc output_ints { ints } {
        variable        gcc_version
        variable        big_endian
        variable        output_list
        
        if { $gcc_version < 30300 } {
            puts stderr "$::program : internal error, there should be no need for writing ints with gcc $ecosxda::gcc_version"
            exit 1
        } elseif { $gcc_version < 30400 } {
            # In gcc 3.3 just output each number in little-endian format
            foreach int $ints {
                lappend output_list [expr ($int >>  0) & 0x00FF]
                lappend output_list [expr ($int >>  8) & 0x00FF]
                lappend output_list [expr ($int >> 16) & 0x00FF]
                lappend output_list [expr ($int >> 24) & 0x00FF]
            }
        } else {
            # In gcc 3.4 each integer is written in target-side format
            foreach int $ints {
                if { $big_endian } {
                    lappend output_list [expr ($int >> 24) & 0x00FF]
                    lappend output_list [expr ($int >> 16) & 0x00FF]
                    lappend output_list [expr ($int >>  8) & 0x00FF]
                    lappend output_list [expr ($int >>  0) & 0x00FF]
                } else {
                    lappend output_list [expr ($int >>  0) & 0x00FF]
                    lappend output_list [expr ($int >>  8) & 0x00FF]
                    lappend output_list [expr ($int >> 16) & 0x00FF]
                    lappend output_list [expr ($int >> 24) & 0x00FF]
                }
            }
        }
    }

    proc output_int { int } {
        output_ints [list $int]
    }

    proc output_gcov_types { data } {
        variable        gcc_version
        variable        big_endian
        variable        output_list
        
        if { $gcc_version < 30400 } {
            # Prior to gcc 3.4 the data was output via __store_gcov_type()
            # in gcov-io.h, effectively little-endian
            foreach gcov_type $data {
                lappend output_list [lindex $gcov_type 7]
                lappend output_list [lindex $gcov_type 6]
                lappend output_list [lindex $gcov_type 5]
                lappend output_list [lindex $gcov_type 4]
                lappend output_list [lindex $gcov_type 3]
                lappend output_list [lindex $gcov_type 2]
                lappend output_list [lindex $gcov_type 1]
                lappend output_list [lindex $gcov_type 0]
            }
        } else {
            # gcc 3.4 onwards, the relevant function is
            # gcov_write_counter() in gcov-io.c. Least significant
            # integer first, then most significant, using target-side
            # endianness.
            foreach gcov_type $data {
                if { $big_endian } {
                    lappend output_list [lindex $gcov_type 4]
                    lappend output_list [lindex $gcov_type 5]
                    lappend output_list [lindex $gcov_type 6]
                    lappend output_list [lindex $gcov_type 7]
                    lappend output_list [lindex $gcov_type 0]
                    lappend output_list [lindex $gcov_type 1]
                    lappend output_list [lindex $gcov_type 2]
                    lappend output_list [lindex $gcov_type 3]
                } else {
                    lappend output_list [lindex $gcov_type 7]
                    lappend output_list [lindex $gcov_type 6]
                    lappend output_list [lindex $gcov_type 5]
                    lappend output_list [lindex $gcov_type 4]
                    lappend output_list [lindex $gcov_type 3]
                    lappend output_list [lindex $gcov_type 2]
                    lappend output_list [lindex $gcov_type 1]
                    lappend output_list [lindex $gcov_type 0]
                }
            }
        }
    }

    proc output_gcov_type { count } {
        output_gcov_types [list $count]
    }
    
    # This is only relevant for gcc 3.3
    proc output_string { str } {
        variable        gcc_version
        variable        output_list
        
        if { ($gcc_version >= 30300) && ($gcc_version < 304000) } {
            set len [string length $str]
            # delimiter and length
            output_ints [list 0x80000001 $len]
            
            set bin [binary format "a*" $str]
            binary scan $bin "c*" numbers
            foreach num $numbers {
                lappend output_list $num
            }
            lappend output_list 0
            set len [expr ($len + 1) & 0x03]
            if { $len } {
                for { set i [expr 4 - $len] } { $i > 0 } { incr i -1 } {
                    lappend output_list 0
                }
            }
            output_ints [list 0x80000001]
        } else {
            puts stderr "$::program : internal error, there should be no need for writing strings with gcc $gcc_version"
            exit 1
        }
    }

    # ----------------------------------------------------------------------------
    # The ecosgcov.out file is held in an array. The format is determined
    # by a compiler version.
    array set data      [list]
    
    # ----------------------------------------------------------------------------
    # Read in the header of an ecosgcov.out file. This format is the same for
    # all compiler versions
    proc parse_header { } {
        input_ensure 28 "ecosgcov.out header" 1
        set data [input_bytes 16]
        if { (0x003d != [lindex $data 0]) || (0x0065 != [lindex $data 1]) || (0x009e != [lindex $data 2]) || (0x00dc != [lindex $data 3]) } {
            puts stderr "$::program : invalid ecosgcov.out file."
            puts stderr "    Incorrect magic numbers at start of file."
            exit 1
        }
        verbose 1 "gcov data produced by gcc [lindex $data 5].[lindex $data 6].[lindex $data 7]"
        if { [lindex $data 8] } {
            verbose 1 "Target is big-endian."
        } else {
            verbose 1 "Target is little-endian."
        }
        
        set ecosxda::gcc_version [expr ([lindex $data 5] * 10000) + ([lindex $data 6] * 100) + [lindex $data 7]]
        set ecosxda::big_endian  [lindex $data 8]
        set ecosxda::sizeof_int  [lindex $data 9]
        set ecosxda::sizeof_ptr  [lindex $data 10]

        # Starting with 3.4 the .gcda files contain very specific
        # compiler version info
        set ecosxda::gcov_magic [expr ((0x30 + [lindex $data 5]) << 24) + ((0x30 + ([lindex $data 6] / 10)) << 16) + \
                                     ((0x30 + ([lindex $data 6] % 10)) << 8) + [lindex $data 12]]

        set data [input_bytes 8]
        set ecosxda::longlong_byte0     [lindex $data 0]

        set ecosxda::program_crc        [input_int]
    }
    
    # ----------------------------------------------------------------------------
    # gcc 3.2.1 support
    proc gcc_3_2_1 { no_output replace } {
        variable        data
        variable        sizeof_int
        variable        sizeof_ptr
        
        verbose 1 "Processing gcov information produced by gcc 3.2.1 or compatible."
        set     file    0
        while { ! [input_done] } {
            input_ensure [expr (3 * $sizeof_int) + (8 * $sizeof_ptr)] "struct gcov_module" 1
            
            # skip zero_word, filename, counts
            input_int
            input_ptrs 2
            set data($file,n_counts) [input_int]
            set data($file,n_countsll) [list 0 0 0 0 \
                                            [expr ($data($file,n_counts) >> 24) & 0x00FF]       \
                                            [expr ($data($file,n_counts) >> 16) & 0x00FF]       \
                                            [expr ($data($file,n_counts) >>  8) & 0x00FF]       \
                                            [expr ($data($file,n_counts) >>  0) & 0x00FF]]
            # skip next, addresses, nwords, functions, line_nums, filenames, flags
            input_ptrs 2
            input_int
            input_ptrs 4
            
            input_ensure 1 "filename" 1
            set data($file,filename) [input_string]

            input_ensure [expr $data($file,n_counts) * 8] "$data($file,n_counts) counters" 1
            set data($file,counts)       [input_gcov_types $data($file,n_counts)]
            verbose 1 "  Parsed data for $data($file,filename), $data($file,n_counts) counts"
            incr file
        }
        set     n_files $file
        verbose 1 "Read in all new gcov data."

        if { ! $replace } {
            verbose 1 "Merging with existing .da files"
            for { set file 0 } { $file < $n_files } { incr file } {
                if { ! [file exists $data($file,filename)] } {
                    verbose 1 "No file $data($file,filename)"
                    continue
                }
                if { ! [file readable $data($file,filename)] } {
                    verbose 1 "No read access to file $data($file,filename)"
                    continue
                }
                if { ! [input_read $data($file,filename) 0] } {
                    continue
                }
                if { ! [input_ensure 8 "number of counters" 0] } {
                    continue
                }
                set n_countersll [input_gcov_type]
                if { ! [gcov_type_eq $n_countersll $data($file,n_countsll)] } {
                    verbose 1 "Mismatch in file $data($file,filename), the number of counters is different."
                    continue
                }
                if { ! [input_ensure [expr 8 * $data($file,n_counts)] "counters" 0] } {
                    continue
                }
                set old_counts [input_gcov_types $data($file,n_counts)]
                set new_counts [list]
                for { set i 0 } { $i < $data($file,n_counts) } { incr i } {
                    lappend new_counts [gcov_type_sum [lindex $data($file,counts) $i] [lindex $old_counts $i]]
                }
                set data($file,counts) $new_counts
                if { ! [input_done] } {
                    verbose 1 "Spurious data at end of $data($file,filename)"
                }
                verbose 1 "Merged existing coverage data from $data($file,filename)"
            }
        }

        if { ! $no_output } {
            for { set file 0 } { $file < $n_files } { incr file } {
                output_reset
                output_gcov_types [list $data($file,n_countsll)]
                output_gcov_types $data($file,counts)
                output_write $data($file,filename)
            }
        }
    }

    # ----------------------------------------------------------------------------
    # gcc 3.3.x support
    proc gcc_3_3_x { no_output replace } {
        variable        data
        variable        sizeof_int
        variable        sizeof_ptr
        array set       program_summary [list]

        verbose 1 "Processing gcov information produced by gcc 3.3.x"

        set file 0
        while { ! [input_done] } {
            # A gcov_module: zero_word, filename, counts, ncounts, next, sizeof_bb, function_infos.
            # Only ncounts is interesting.
            input_ensure [expr (3 * $sizeof_int) + (4 * $sizeof_ptr)] "struct gcov_module" 1
            input_int
            input_ptrs 2
            set data($file,n_counts) [input_int]
            input_ptr
            input_int
            input_ptr
            # Now the filename
            input_ensure 1 "filename" 1
            set data($file,filename) [input_string]
            # The counts
            input_ensure [expr 8 * $data($file,n_counts)] "counts" 1
            set data($file,counts) [input_gcov_types $data($file,n_counts)]
            # The number of functions
            input_ensure $sizeof_int "number of functions" 1
            set data($file,n_functions) [input_int]
            # The table of function infos
            for { set i 0 } { $i < $data($file,n_functions) } { incr i } {
                input_ensure [expr (2 * $sizeof_int) + $sizeof_ptr] "function infos\[\]" 1
                set data($file,fn$i,checksum)   [input_int]
                set data($file,fn$i,arc_count)  [input_int]
                input_ptr
            }
            # And a name for each function
            for { set i 0 } { $i < $data($file,n_functions) } { incr i } {
                input_ensure 1 "function name" 1
                set data($file,fn$i,name) [input_string]
            }
            verbose 1 "  Parsed data for $data($file,filename)"
            verbose 2 "    file has $data($file,n_functions) functions, $data($file,n_counts) counts"
            for { set i 0 } { $i < $data($file,n_functions) } { incr i } {
                verbose 3 "      Function $data($file,fn$i,name) has crc [format {0x%08x} $data($file,fn$i,checksum)], $data($file,fn$i,arc_count) arcs"
            }
            incr file
        }
        set n_files $file
        verbose 1 "Read in all new gcov data."

        # Now calculate the summary info.
        set program_summary(arcs)       0
        set program_summary(sum)        $ecosxda::gcov_type_zero
        set program_summary(max)        $ecosxda::gcov_type_zero
        for { set file 0 } { $file < $n_files } { incr file } {
            set data($file,summary,arcs)        $data($file,n_counts)
            set data($file,summary,sum)         $ecosxda::gcov_type_zero
            set data($file,summary,max)         $ecosxda::gcov_type_zero
            foreach count $data($file,counts) {
                set data($file,summary,sum) [gcov_type_sum $data($file,summary,sum) $count]
                set data($file,summary,max) [gcov_type_max $data($file,summary,max) $count]
            }
        }
        for { set file 0 } { $file < $n_files } { incr file } {
            incr program_summary(arcs)  $data($file,n_counts)
            set  program_summary(sum)   [gcov_type_sum $program_summary(sum) $data($file,summary,sum)]
            set  program_summary(max)   [gcov_type_max $program_summary(max) $data($file,summary,max)]
        }
        for { set file 0 } { $file < $n_files } { incr file } {
            set data($file,program,arcs)        $program_summary(arcs)
            set data($file,program,sum)         $program_summary(sum)
            set data($file,program,max)         $program_summary(max)
        }
        verbose 1 "Program summary before merging:"
        verbose 1 "  Number of arcs    $program_summary(arcs)"
        verbose 1 [format "  Sum of all counts 0x%02x%02x%02x%02x%02x%02x%02x%02x"              \
                       [lindex $program_summary(sum) 0] [lindex $program_summary(sum) 1]        \
                       [lindex $program_summary(sum) 2] [lindex $program_summary(sum) 3]        \
                       [lindex $program_summary(sum) 4] [lindex $program_summary(sum) 5]        \
                       [lindex $program_summary(sum) 6] [lindex $program_summary(sum) 7]]
        verbose 1 [format "  Max count         0x%02x%02x%02x%02x%02x%02x%02x%02x"              \
                       [lindex $program_summary(max) 0] [lindex $program_summary(max) 1]        \
                       [lindex $program_summary(max) 2] [lindex $program_summary(max) 3]        \
                       [lindex $program_summary(max) 4] [lindex $program_summary(max) 5]        \
                       [lindex $program_summary(max) 6] [lindex $program_summary(max) 7]]

        if { ! $replace } {
            verbose 1 "Merging with existing .da files"

            for { set file 0 } { $file < $n_files } { incr file } {
                set data($file,old,ok) 0
                if { ! [file exists $data($file,filename)] } {
                    verbose 1 "No file $data($file,filename)"
                    continue
                }
                if { ! [file readable $data($file,filename)] } {
                    verbose 1 "No read access to file $data($file,filename)"
                    continue
                }
                if { ! [input_read $data($file,filename) 0] } {
                    continue
                }
                if { ! [input_ensure [expr 3 * $sizeof_int] "header" 0 ] } {
                    continue
                }
		# Possible 32/64 arithmetic problems here with different versions of Tcl
		set magic [input_int]
		if { (0x0080 != (($magic >> 24) & 0x00FF)) || (0x0000007b != ($magic & 0x00FFFFFF)) } {
                    verbose 1 "File $data($file,filename) is not in .da format"
                    continue
                }
                if { $data($file,n_functions) != [input_int] } {
                    verbose 1 "File $data($file,filename) mismatch, different number of functions"
                    continue
                }
                if { (2 * ($sizeof_int + 8 + 8)) != [input_int] } {
                    verbose 1 "File $data($file,filename) mismatch, wrong summary size"
                    continue
                }
                if { ! [input_ensure [expr 2 * ($sizeof_int + 8 + 8)] "summaries" 0] } {
                    continue
                }
                if { $data($file,program,arcs) != [input_int] } {
                    verbose 1 "File $data($file,filename) mismatch, different number of program arcs"
                    continue
                }
                set data($file,old,program,sum) [input_gcov_type]
                set data($file,old,program,max) [input_gcov_type]
                if { $data($file,summary,arcs) != [input_int] } {
                    verbose 1 "File $data($file,filename) mismatch, different number of summary arcs"
                    continue
                }
                set data($file,old,summary,sum) [input_gcov_type]
                set data($file,old,summary,max) [input_gcov_type]

                set ok 1
                set data($file,old,counts)      [list]
                for { set i 0 } { $ok && ($i < $data($file,n_functions)) } { incr i } {
                    if { ! [input_ensure [expr (2 * $sizeof_int) + 1] "string header" 0] } {
                        break
                    }
                    input_ints 2
                    input_string

                    if { ! [input_ensure [expr 3 * $sizeof_int] "function header" 0] } {
                        set ok 0
                        break
                    }
                    input_int
                    if { $data($file,fn$i,checksum) != [input_int] } {
                        verbose 1 "File $data($file,filename) mismatch, different checksum, file has been recompiled"
                        set ok 0
                        break
                    }
                    if { $data($file,fn$i,arc_count) != [input_int] } {
                        verbose 1 "File $data($file,filename) mismatch, different arc count"
                        set ok 0
                        break
                    }
                    if { ! [input_ensure [expr 8 * $data($file,fn$i,arc_count)] "function counts" 0 ] } {
                        set ok 0
                        break
                    }
                    foreach count [input_gcov_types $data($file,fn$i,arc_count)] {
                        lappend data($file,old,counts) $count
                    }
                }
                if { $ok && ![input_done] } {
                    verbose 1 "Spurious data at end of $data($file,filename)"
                    set ok 0
                }
                if { $ok } {
                    verbose 1 "Read existing coverage data from $data($file,filename)"
                    set data($file,old,ok) 1
                }
            }

            for { set file 0 } { $file < $n_files } { incr file } {
                if { ! $data($file,old,ok) } {
                    continue
                }
                set data($file,program,sum)     [gcov_type_sum $data($file,program,sum) $data($file,old,program,sum)]
                set data($file,program,max)     [gcov_type_max $data($file,program,max) $data($file,old,program,max)]
                set data($file,summary,sum)     [gcov_type_sum $data($file,summary,sum) $data($file,old,summary,sum)]
                set data($file,summary,max)     [gcov_type_max $data($file,summary,max) $data($file,old,summary,max)]

                set new_counts [list]
                for { set i 0 } { $i < $data($file,n_counts) } { incr i } {
                    lappend new_counts [gcov_type_sum [lindex $data($file,counts) $i] [lindex $data($file,old,counts) $i]]
                }
                set data($file,counts) $new_counts
            }
        }

        if { ! $no_output } {
            for { set file 0 } { $file < $n_files } { incr file } {
                output_reset
                # Magic, no of functions, length of extra bytes (the summaries), program_arcs
                output_ints [list 0x8000007b $data($file,n_functions) [expr 2 * ($sizeof_int + 8 + 8)] $data($file,program,arcs)]

                # The rest of the summaries
                output_gcov_types [list $data($file,program,sum) $data($file,program,max)]
                output_int $data($file,summary,arcs)
                output_gcov_types [list $data($file,summary,sum) $data($file,summary,max)]

                # For each function, string, checksum, arc count and the arcs
                set arc_index 0
                for { set i 0 } { $i < $data($file,n_functions) } { incr i } {
                    output_string $data($file,fn$i,name)
                    output_ints [list $data($file,fn$i,checksum) $data($file,fn$i,arc_count)]
                    output_gcov_types [lrange $data($file,counts) $arc_index [expr $arc_index + $data($file,fn$i,arc_count) - 1]]
                    incr arc_index $data($file,fn$i,arc_count)
                }
                
                output_write $data($file,filename)
            }
        }
    }

    # ----------------------------------------------------------------------------
    # gcc 3.4.x support
    
    proc gcc_3_4_x { no_output replace } {
        variable        data
        variable        sizeof_int
        variable        sizeof_ptr
        array set       program_summary [list]

        verbose 1 "Processing gcov information produced by gcc 3.4 or compatible."

        set file 0
        while { ! [input_done] } {
            input_ensure [expr (4 * $sizeof_int) + (3 * $sizeof_ptr)] "struct gcov_module" 1
            set data($file,version)     [input_int]
            input_ptr
            set data($file,stamp)       [input_int]
            input_ptr
            set data($file,n_functions) [input_int]
            input_ptr
            set data($file,ctr_mask)    [input_int]

            set n_counters 0
            for { set i 0 } { $i < 32 } { incr i } {
                if { 0 != ($data($file,ctr_mask) & (0x01 << $i)) } {
                    incr n_counters 1
                }
            }
            set data($file,n_counters)  $n_counters

            input_ensure [expr $n_counters * ((2 * $sizeof_int) + $sizeof_ptr)] "struct gcov_ctr_info\[\]" 1
            for { set i 0 } { $i < $n_counters } { incr i } {
                set data($file,ctr$i,num)               [input_int]
                input_ptr
                set data($file,ctr$i,merge_id)  [input_int]
            }
            
            input_ensure 1 "filename" 1
            set data($file,filename)            [input_string]

            input_ensure [expr $data($file,n_functions) * ((2 + $n_counters) * $sizeof_int)] "struct gcov_fn_info \[\]" 1
            for { set i 0 } { $i < $data($file,n_functions) } { incr i } {
                set data($file,fn$i,ident) [input_int]
                set data($file,fn$i,checksum) [input_int]
                for { set j 0 } { $j < $n_counters } { incr j } {
                    set data($file,fn$i,n_ctrs$j)       [input_int]
                }
            }

            for { set i 0 } { $i < $n_counters } { incr i } {
                input_ensure [expr $data($file,ctr$i,num) * 8] "counters" 1
                set data($file,counts$i)        [input_gcov_types $data($file,ctr$i,num)]
            }
            verbose 1 "  Parsed data for $data($file,filename)"
            verbose 2 "    object file version $data($file,version), time stamp [format {0x%08x} $data($file,stamp)]"
            verbose 2 "    file has $data($file,n_functions) functions, $data($file,n_counters) instrumentation counters"
            for { set i 0 } { $i < $n_counters } { incr i } {
                verbose 3 "      counter $i has $data($file,ctr$i,num) entries"
            }
            for { set i 0 } { $i < $data($file,n_functions) } { incr i } {
                verbose 3 "      fn $i has id $data($file,fn$i,ident), crc [format {0x%08x} $data($file,fn$i,checksum)]"
            }
            incr file 1
        }
        set     n_files $file
        verbose 1 "Read in all new gcov data."

        # Now calculate all the summary information. This only applies to
        # counter zero (strictly speaking up to GCOV_COUNTERS_SUMMABLE, but
        # that is currently defined as 1)
        set program_summary(num)        0
        set program_summary(runs)       1
        set program_summary(sum_all)    $ecosxda::gcov_type_zero
        set program_summary(run_max)    $ecosxda::gcov_type_zero
        set program_summary(sum_max)    $ecosxda::gcov_type_zero
        for { set file 0 } { $file < $n_files } { incr file } {
            set data($file,summary,num) $data($file,ctr0,num)
            set data($file,summary,runs)        1
            set data($file,summary,sum_all)     $ecosxda::gcov_type_zero
            set data($file,summary,run_max)     $ecosxda::gcov_type_zero
            foreach count $data($file,counts0) {
                set data($file,summary,sum_all) [gcov_type_sum $data($file,summary,sum_all) $count]
                set data($file,summary,run_max) [gcov_type_max $data($file,summary,run_max) $count]
            }
            set data($file,summary,sum_max) $data($file,summary,sum_all)
            
            incr program_summary(num)    $data($file,summary,num)
            set program_summary(sum_all) [gcov_type_sum $program_summary(sum_all) $data($file,summary,sum_all)]
            set program_summary(run_max) [gcov_type_max $program_summary(run_max) $data($file,summary,run_max)]
        }
        set program_summary(sum_max) $program_summary(sum_all)
        verbose 1 "Program summary before merging:"
        verbose 1  "  Run               $program_summary(runs)"
        verbose 1 "  Number of arcs    $program_summary(num)"
        verbose 1 [format "  Sum of all counts 0x%02x%02x%02x%02x%02x%02x%02x%02x"                      \
                       [lindex $program_summary(sum_all) 0] [lindex $program_summary(sum_all) 1]        \
                       [lindex $program_summary(sum_all) 2] [lindex $program_summary(sum_all) 3]        \
                       [lindex $program_summary(sum_all) 4] [lindex $program_summary(sum_all) 5]        \
                       [lindex $program_summary(sum_all) 6] [lindex $program_summary(sum_all) 7]]
        verbose 1 [format "  Max count         0x%02x%02x%02x%02x%02x%02x%02x%02x"                      \
                       [lindex $program_summary(run_max) 0] [lindex $program_summary(run_max) 1]        \
                       [lindex $program_summary(run_max) 2] [lindex $program_summary(run_max) 3]        \
                       [lindex $program_summary(run_max) 4] [lindex $program_summary(run_max) 5]        \
                       [lindex $program_summary(run_max) 6] [lindex $program_summary(run_max) 7]]
        verbose 1 [format "  Sum max           0x%02x%02x%02x%02x%02x%02x%02x%02x"                      \
                       [lindex $program_summary(sum_max) 0] [lindex $program_summary(sum_max) 1]        \
                       [lindex $program_summary(sum_max) 2] [lindex $program_summary(sum_max) 3]        \
                       [lindex $program_summary(sum_max) 4] [lindex $program_summary(sum_max) 5]        \
                       [lindex $program_summary(sum_max) 6] [lindex $program_summary(sum_max) 7]]

        for { set file 0 } { $file < $n_files } { incr file } {
            set data($file,program,num)         $program_summary(num)
            set data($file,program,runs)        $program_summary(runs)
            set data($file,program,sum_all)     $program_summary(sum_all)
            set data($file,program,run_max)     $program_summary(run_max)
            set data($file,program,sum_max)     $program_summary(sum_max)
        }
        
        if { ! $replace } {
            verbose 1 "Merging with existing .gcda files"
            
            for { set file 0 } { $file < $n_files } { incr file } {
                set data($file,old,ok)  0
                if { ! [file exists $data($file,filename)] } {
                    verbose 1 "No file $data($file,filename)"
                    continue
                }
                if { ! [file readable $data($file,filename)] } {
                    verbose 1 "No read access to file $data($file,filename)"
                    continue
                }
                if { ! [input_read $data($file,filename) 0] } {
                    continue
                }

                if { ! [input_ensure [expr 4 * $sizeof_int] "header" 0] } {
                    continue
                }
                if { (0x67636461 == [input_int]) && ($ecosxda::gcov_magic != [input_int]) } {
                    verbose 1 "File $data($file,filename) is not in .gcda format"
                    continue
                }
                if { $data($file,stamp) != [input_int] } {
                    verbose 1 "File $data($file,filename) is for a different build"
                    continue
                }
                set ok 1
                for { set tag [input_int] ; set i 0 } { $ok && ($tag == 0x01000000) } { set tag [input_int] ; incr i } {
                    if { ! [input_ensure [expr 3 * $sizeof_int] "function header" 0 ] } {
                        continue
                    }
                    if { (2 != [input_int]) || \
                             ($data($file,fn$i,ident) != [input_int]) || \
                             ($data($file,fn$i,checksum) != [input_int]) } {
                        verbose 1 "Id mismatch for function $i in $data($file,filename)"
                        set ok 0
                        break
                    }
                    
                    # The tags used are rather bogus, it is not
                    # possible to loop while there is a valid tag.
                    # Instead loop for the expected number of counters
                    for { set j 0 } { $j < $data($file,n_counters) } { incr j } {
                        if { ! [input_ensure [expr 2 * $sizeof_int] "arc counts header" 0] } {
                            set ok 0
                            break
                        }
                        set tag [input_int]
                        if { $tag != (0x01a10000 | $j << 17) } {
                            verbose 1 "Tag mismatch for function $i in $data($file,filename)"
                            set ok 0
                            break
                        }
                        set n_counts    [expr [input_int] / 2]
                        if { $n_counts != $data($file,fn$i,n_ctrs$j) } {
                            verbose 1 "Counts length mismatch for function $i in $data($file,filename)"
                            set ok 0
                            break
                        }
                        if { ! [input_ensure [expr 8 * $n_counts] "counts" 0] } {
                            set ok 0
                            break
                        }
                        foreach counter [input_gcov_types $n_counts] {
                            lappend data($file,old,counts$j) $counter
                        }
                    }
                }
                if { ! $ok } {
                    continue
                }
                # Next should come the object summary and the program summary
                if { ! [input_ensure [expr (9 * $sizeof_int) + (6 * 8)] "summaries" 0] } {
                    continue
                }
                if { (0xa1000000 != ($tag & 0x0FFFFFFFF)) || \
                         (9 != [input_int])               || \
                         (0 != [input_int])               || \
                         ($data($file,summary,num) != [input_int]) } {
                    verbose 1 "Mismatch on object file summary in $data($file,filename)"
                    continue
                }
                set data($file,old,summary,runs)        [input_int]
                set data($file,old,summary,sum_all)     [input_gcov_type]
                set data($file,old,summary,run_max)     [input_gcov_type]
                set data($file,old,summary,sum_max)     [input_gcov_type]

                if { (0xa3000000 != ([input_int] & 0x0FFFFFFFF)) || (9 != [input_int]) } {
                    verbose 1 "Mismatch on program summary in $data($file,filename)"
                    continue
                }
                if { $ecosxda::program_crc != [input_int] } {
                    verbose 1 "File $data($file,filename) corresponds to a different build"
                    continue
                }
                set data($file,old,program,num) [input_int]
                set data($file,old,program,runs)        [input_int]
                set data($file,old,program,sum_all)     [input_gcov_type]
                set data($file,old,program,run_max)     [input_gcov_type]
                set data($file,old,program,sum_max)     [input_gcov_type]

                if { ! [input_done] } {
                    verbose 1 "Spurious data at end of $data($file,filename)"
                    continue
                }
                # All ok
                verbose 1 "Read existing coverage data from $data($file,filename)"
                set data($file,old,ok) 1
            }

            for { set file 0 } { $file < $n_files } { incr file } {
                if { ! $data($file,old,ok) } {
                    # No info available for this file
                    continue
                }
                for { set i 0 } { $i < $data($file,n_counters) } { incr i } {
                    # FIXME: this code should be checking the merge id and perform
                    # the appropriate merge function, not just add the numbers
                    set new_counters [list]
                    for { set j 0 } { $j < $data($file,ctr$i,num) } { incr j } {
                        lappend new_counters [gcov_type_sum [lindex $data($file,counts$i) $j] [lindex $data($file,old,counts$i) $j]]
                    }
                    set data($file,counts$i) $new_counters
                }
                set data($file,summary,runs)    [expr $data($file,old,summary,runs) + 1]
                set data($file,summary,sum_max) [gcov_type_max $data($file,summary,sum_all) $data($file,old,summary,sum_max)]
                set data($file,summary,sum_all) [gcov_type_sum $data($file,summary,sum_all) $data($file,old,summary,sum_all)]
                set data($file,summary,run_max) [gcov_type_max $data($file,summary,run_max) $data($file,old,summary,run_max)]
                set data($file,program,runs)    [expr $data($file,program,runs) + $data($file,old,program,runs)]
                set data($file,program,sum_max) [gcov_type_max $data($file,program,sum_all) $data($file,old,program,sum_max)]
                set data($file,program,sum_all) [gcov_type_max $data($file,program,sum_all) $data($file,old,program,sum_max)]
                set data($file,program,run_max) [gcov_type_max $data($file,program,run_max) $data($file,old,program,run_max)]
            }
        }

        if { ! $no_output } {
            for { set file 0 } { $file < $n_files } { incr file } {
                output_reset
                # gcov_write_tag_length(GCOV_DATA_MAGIC, GCOV_VERSION)
                # gcov_write_unsigned(gi_ptr->stamp)
                output_ints [list 0x67636461 $ecosxda::gcov_magic $data($file,stamp)]

                for { set i 0 } { $i < $data($file,n_counters) } { incr i } {
                    set ctr_idx($i)     0
                }
                
                for { set i 0 } { $i < $data($file,n_functions) } { incr i } {
                    # GCOV_TAG_FUNCTION, GCOV_TAG_FUNCTION_LENGTH, ident & checksum
                    output_ints [list 0x01000000 2 $data($file,fn$i,ident) $data($file,fn$i,checksum)]
                    for { set j 0 } { $j < $data($file,n_counters) } { incr j } {
                        set count $data($file,fn$i,n_ctrs$j)
                        # GCOV_TAG_FOR_COUNTER(t_ix), GCOV_TAG_COUNTER_LENGTH(n_counts)
                        output_ints [list [expr 0x01a10000 | ($j << 17)] [expr 2 * $count]]
                        output_gcov_types [lrange $data($file,counts$j) $ctr_idx($j) [expr ($ctr_idx($j) + $count - 1)]]
                        incr ctr_idx($j) $count
                    }
                }

                # Now for the file summary
                # GCOV_TAG_OBJECT_SUMMARY, length, checksum, num, runs and the data
                output_ints [list 0xa1000000 9 0 $data($file,summary,num) $data($file,summary,runs)]
                output_gcov_types [list $data($file,summary,sum_all) $data($file,summary,run_max) $data($file,summary,sum_max)]

                # And the whole program summary
                # GCOV_TAG_PROGRAM_SUMMARY, length, checksum, num, runs and hte data
                output_ints [list 0xa3000000 9 $ecosxda::program_crc $data($file,program,num) $data($file,program,runs)]
                output_gcov_types [list $data($file,program,sum_all) $data($file,program,run_max) $data($file,program,sum_max)]

                output_write $data($file,filename)
            }
        }
    }
}

# Allow this script to be used either stand-alone or by other more
# advanced scripts.
#
# A useful extension might be -name, restricting the output to just
# those files matching a glob expression.

if { 0 == [llength [info procs main]] } {
    proc main_usage { fd exitcode } {
        puts $fd "$::program : usage, $::program \[-v\] \[-V\] \[-h\] \[-n\] \[-r\] \[filename\]"
        puts $fd ""
        puts $fd "Extract test coverage data from an ecosgcov.out file"
        puts $fd ""
        puts $fd "  -h, --help      show this help text"
        puts $fd "  -v, --verbose   display additional diagnostics"
        puts $fd "  -V, --version   show the program's version number"
        puts $fd "  -n, --no-output do not create or modify the .da output files"
        puts $fd "  -r, --replace   replace existing .da files rather than append to"
        puts $fd "                  the current data"
        puts $fd "  filename        use the specified file instead of ecosgcov.out"
        exit $exitcode
    }

    proc main_version { } {
        puts "$::program : version $::version"
        exit 0
    }

    proc main { argv } {
        set     no_output_flag  0
        set     replace_flag    0
        set     filename        ""
        set     seen_double     0

        foreach arg $argv {
            if { [string equal $arg "--help"] } {
                main_usage stdout 0
            } elseif { [string equal $arg "--verbose"] } {
                incr ::verbose
            } elseif { [string equal $arg "--version"] } {
                main_version
            } elseif { [string equal $arg "--no-output"] } {
                set no_output_flag      1
            } elseif { [string equal $arg "--replace"] } {
                set replace_flag        1
            } elseif { [string equal $arg "--"] } {
                # This allows users to specify a filename that begins with a hyphen
                set seen_double 1
            } elseif { [string equal -length 1 $arg "-"] && ! $seen_double } {
                for { set i 1 } { $i < [string length $arg] } { incr i } {
                    set flag [string index $arg $i]
                    if { [string equal $flag "h"] } {
                        main_usage stdout 0
                    } elseif { [string equal $flag "V"] } {
                        main_version
                    } elseif { [string equal $flag "v"] } {
                        incr ::verbose
                    } elseif { [string equal $flag "n"] } {
                        set no_output_flag 1
                    } elseif { [string equal $flag "r"] } {
                        set replace_flag 1
                    } else {
                        puts stderr "$::program : unknown flag -$flag"
                        main_usage stderr 1
                    }
                }
            } else {
                if { ! [string equal $filename ""] } {
                    puts stderr "$::program : only one input file should be specified"
                    main_usage stderr 1
                } else {
                    set filename $arg
                }
            }
        }
        
        if { [string equal "" $filename] } {
            set filename "ecosgcov.out"
        }
        if { ! ( [file exists $filename] && [file readable $filename] ) } {
            puts stderr "$::program : unable to read file \"$filename\""
            main_usage stderr 1
        }

        if { ! [ecosxda::input_read $filename 1] } {
            exit 1
        }
        ecosxda::parse_header
        if { $ecosxda::gcc_version < 30300 } {
            ecosxda::gcc_3_2_1 $no_output_flag $replace_flag
        } elseif { $ecosxda::gcc_version < 30400 } {
            ecosxda::gcc_3_3_x $no_output_flag $replace_flag
        } else {
            ecosxda::gcc_3_4_x $no_output_flag $replace_flag
        }
    }
    main $::argv
}
