From patchwork Sat Jan 31 23:47:52 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Siarhei Siamashka X-Patchwork-Id: 435198 X-Patchwork-Delegate: trini@ti.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from theia.denx.de (theia.denx.de [85.214.87.163]) by ozlabs.org (Postfix) with ESMTP id B5718140142 for ; Sun, 1 Feb 2015 10:48:07 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id C1D614A033; Sun, 1 Feb 2015 00:48:03 +0100 (CET) Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id F0Vm134dW9Jp; Sun, 1 Feb 2015 00:48:03 +0100 (CET) Received: from theia.denx.de (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 0ACD64A02C; Sun, 1 Feb 2015 00:48:03 +0100 (CET) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id BE6FA4A02C for ; Sun, 1 Feb 2015 00:47:58 +0100 (CET) Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 5_xOoTX4Vv9N for ; Sun, 1 Feb 2015 00:47:58 +0100 (CET) X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_SPAMCOP=-1.5 NOT_IN_BL_NJABL=-1.5 (only DNSBL check requested) Received: from mail-la0-f53.google.com (mail-la0-f53.google.com [209.85.215.53]) by theia.denx.de (Postfix) with ESMTPS id 573294A02A for ; Sun, 1 Feb 2015 00:47:56 +0100 (CET) Received: by mail-la0-f53.google.com with SMTP id gq15so31134160lab.12 for ; Sat, 31 Jan 2015 15:47:55 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:subject:date:message-id:mime-version:content-type :content-transfer-encoding; bh=7+J3Dx7s0t8vhgSli1XmqDmWjTIyNVdZvenQhYEX12c=; b=IBrD2k1qdE1X6MUwmbhkc9/djdD4QxAwXMkcgbH4sAgErueDC3onmEeKED3ETy1HZ7 CtjBNEenDOxy1+Sjw/Wtsx0dRrgxF6DEqJFQRDZaXhxX6PfulRJ6f9cL3WsWY2+HZqk6 6hfbWI9Q/avahL/m3hEuss4vabl6zFcbD9rqyWFEVOCARp2XQWUa7xtu3Ok+qk1ardB0 7UyjuwPjiCKznCHRpXfN1Vu0UB+e7pgCczN8vyeNRZYXdeT2aB1EM8YFiBzRz8/je9ZI EzuYPquDwLvnJgESS2qsuujrys7IE4R9fcXnQYDA4oGBbGYusuyMH/6N4bd8nDI7kXG3 jESg== X-Received: by 10.112.37.161 with SMTP id z1mr8953467lbj.87.1422748075207; Sat, 31 Jan 2015 15:47:55 -0800 (PST) Received: from localhost.localdomain (85-76-74-251-nat.elisa-mobile.fi. [85.76.74.251]) by mx.google.com with ESMTPSA id rp6sm3535501lbb.4.2015.01.31.15.47.53 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Sat, 31 Jan 2015 15:47:54 -0800 (PST) From: Siarhei Siamashka To: u-boot@lists.denx.de Date: Sun, 1 Feb 2015 01:47:52 +0200 Message-Id: <1422748072-32405-1-git-send-email-siarhei.siamashka@gmail.com> X-Mailer: git-send-email 2.0.5 MIME-Version: 1.0 Subject: [U-Boot] [RFC/PATCH] tools: New 'spl-stackusage' script X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.15 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" This is a script, which tries to provide a pessimistic estimate of the stack usage in the SPL binary. A more detailed description about how it works and some example pictures are available in an earlier e-mail: http://lists.denx.de/pipermail/u-boot/2015-January/201713.html In fact, I have done nothing with it since that old status update and the code is still very much just at the proof of concept stage and can be only treated as a somewhat working prototype. It only supports 32-bit ARM so far. An example of trying it: 1. First compile u-boot for some ARM board. For example: make CROSS_COMPILE=arm-none-linux-gnueabi- Cubieboard_defconfig make CROSS_COMPILE=arm-none-linux-gnueabi- -j8 2. Run the script: tools/spl-stackusage/spl-stackusage 3. Look at the (slightly reformatted) output: Execution path with maximized stack usage (3152 bytes): entry > _main > board_init_f > board_init_r > spl_mmc_load_image > mmc_init > mmc_startup > mmc_switch.isra.1 > mmc_send_status > printf > puts > console_puts ? mmc_bread > mmc_set_blocklen > mmc_send_cmd ? sunxi_mmc_getcd > gpio_get_value > axp_gpio_get_value > axp209_read > i2c_read ? twsi_i2c_read > i2c_begin > twsi_wait > udelay > __udelay The '>' symbols indicate normal calls. The '?' symbols indicate a suspected indirect call. Now we can see that the 'console_puts ? mmc_bread' indirect call does not make any sense. So we can run the script again, now indicating that a call from 'console_puts' to 'mmc_bread' is impossible: 4. Run the script again as: tools/spl-stackusage/spl-stackusage \ remove_arrow[console_puts,mmc_bread] 5. Look at the new output: Execution path with maximized stack usage (3152 bytes): entry > _main > board_init_f > board_init_r > spl_mmc_load_image > mmc_init > mmc_startup > mmc_switch.isra.1 > mmc_send_status > printf > puts > console_puts ? sunxi_mmc_getcd > gpio_get_value > axp_gpio_get_value > axp209_read > i2c_read ? mmc_bread > mmc_set_blocklen > mmc_send_cmd ? twsi_i2c_read > i2c_begin > twsi_wait > udelay > __udelay Now it tries to guess that there is an indirect call from 'console_puts' to 'sunxi_mmc_getcd'. This also does not make sense. We add it to the black list too. And repreat the whole process iteratively. Eventually we end up with something like 6. Run the script as: tools/spl-stackusage/spl-stackusage \ remove_arrow[console_puts,mmc_bread] \ remove_arrow[console_puts,sunxi_mmc_getcd] \ remove_arrow[i2c_read,sunxi_mmc_set_ios] \ remove_arrow[console_puts,twsi_i2c_read] \ remove_arrow[i2c_read,mmc_bread] \ remove_arrow[serial_puts,mmc_bread] \ remove_arrow[serial_puts,sunxi_mmc_getcd] \ remove_arrow[console_puts,twsi_i2c_write] \ remove_arrow[console_puts,sunxi_mmc_send_cmd] \ remove_arrow[console_puts,twsi_i2c_probe] \ remove_arrow[serial_puts,twsi_i2c_read] \ remove_arrow[console_puts,twsi_i2c_init] \ remove_arrow[serial_puts,twsi_i2c_write] \ remove_arrow[serial_puts,sunxi_mmc_send_cmd] \ remove_arrow[serial_puts,twsi_i2c_probe] \ remove_arrow[console_puts,sunxi_mmc_set_ios] \ remove_arrow[serial_puts,twsi_i2c_init] 7. And look at the final report: Execution path with maximized stack usage (3064 bytes): entry > _main > board_init_f > board_init_r > spl_mmc_load_image > mmc_init > mmc_startup > mmc_set_clock > mmc_set_ios ? mmc_bread > mmc_set_blocklen > mmc_send_cmd ? sunxi_mmc_getcd > gpio_get_value > axp_gpio_get_value > axp209_read > i2c_read > i2c_get_adapter > printf > vsprintf > vsnprintf_internal.isra.6 > number.isra.4 > put_dec > __div64_32 > __aeabi_uidiv > __aeabi_idiv0 The process of blacklisting impossible caller->callee pairs made the stack usage report less pessimistic (from 3152 to 3064 bytes) and the execution path makes a little bit more sense. But that's the whole point. Even the initial stack usage number is supposed to be usable, because it is just overestimating the stack usage and should never underestimate it (unless confused by tricks in the assembly code). Also there is a 'spl-stackusage.png' file with the whole callgraph generated by the graphviz 'dot' tool. It may looks like this (a different picture, not generated by following the steps above): http://people.freedesktop.org/~siamashka/files/20150116-spl-stackgraph/spl-stackgraph-v2015.01-cubieboard2-fel.png This is surely not intended to be pushed as-is yet. And still needs some cleanups. However I would like to get some feedback, just to get a better sense of direction where it should be moving. Also, formally, the merge window is still open. So, I guess, this tool might have a very slim theoretical chance to get into v2015.04 :-) --- tools/spl-stackusage/spl-stackusage | 1 + tools/spl-stackusage/spl-stackusage.rb | 471 +++++++++++++++++++++++++++++++++ 2 files changed, 472 insertions(+) create mode 120000 tools/spl-stackusage/spl-stackusage create mode 100755 tools/spl-stackusage/spl-stackusage.rb diff --git a/tools/spl-stackusage/spl-stackusage b/tools/spl-stackusage/spl-stackusage new file mode 120000 index 0000000..eb10b9e --- /dev/null +++ b/tools/spl-stackusage/spl-stackusage @@ -0,0 +1 @@ +spl-stackusage.rb \ No newline at end of file diff --git a/tools/spl-stackusage/spl-stackusage.rb b/tools/spl-stackusage/spl-stackusage.rb new file mode 100755 index 0000000..a135d6c --- /dev/null +++ b/tools/spl-stackusage/spl-stackusage.rb @@ -0,0 +1,471 @@ +#!/usr/bin/env ruby +# +# Copyright © 2015 Siarhei Siamashka +# +# 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 +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +############################################################################### +# This is a u-boot SPL stack usage analysis script. Needs to be run # +# from the root of the u-boot source tree after a successful SPL build. # +# # +# Produces 'spl-stackusage.png' file as the output. # +############################################################################### + +require 'find' + +############################################################################### +# Process command line arguments +# +# add_node[node_name, stack_usage] +# remove_node[node_name, stack_usage] +# add_arrow[node_name1, node_name2] +# remove_arrow[node_name1, node_name2] +# +############################################################################### + +$removed_arrows = {} + +ARGV.each {|a| + a.split(";").each {|a| + if a =~ /remove_arrow[\(\[]\s*(\S+)\s*\,\s*(\S+)\s*[\)\]]/ + $removed_arrows[[$1, $2]] = 1 + end + } +} + +############################################################################### + +if !File.directory?("spl") + abort("Error: can't find the 'spl' directory.\n" + + "Please ensure that you are in the root of the u-boot source tree.\n") +end + +def tool_exists(tool_name) + `which #{tool_name} > /dev/null 2>&1` + return $?.to_i == 0 +end + +if !tool_exists("dot") + abort("Please install the graphviz dot tool.\n") +end + +############################################################################### +# Get objdump log # +############################################################################### + +# Get the information from the environment +$toolchain = ENV["CROSS_COMPILE"] + +# Try to guess the ARM toolchain automatically +if !$toolchain && `file spl/u-boot-spl` =~ / ARM, / + toolchain_list = [ + "arm-none-linux-gnueabi-", + "armv7a-hardfloat-linux-gnueabi-", + "arm-linux-gnueabihf-" + ] + toolchain_list.each {|toolchain| + if tool_exists("#{toolchain}objdump") + $toolchain = toolchain + break + end + } +end + +if !$toolchain + abort("Error: can't find a usable toolchain.\n" + + "Try to set the CROSS_COMPILE environment variable.\n") +end + +$objdump_dis_log = `#{$toolchain}objdump -d -M reg-names-raw spl/u-boot-spl` +$objdump_sym_log = `#{$toolchain}objdump -t spl/u-boot-spl` +$size_log = `#{$toolchain}size spl/u-boot-spl` + +File.open("spl-stackusage.dis", "w").write($objdump_dis_log) +File.open("spl-stackusage.sym", "w").write($objdump_sym_log) + +############################################################################### +# Read the *.su files (generated by GCC with '-fstack-usage' option) # +############################################################################### + +$dynamic_stack_size = {} +$self_stack_size = {} +$total_stack_size = {} + +Find.find("spl") { |filename| + next unless filename =~ /.*\.su$/ + File.open(filename).each_line {|l| + if l =~ /\:([^\:\s]+)\s+(\d+)\s+(\S+)$/ + case $3 + when "dynamic" + $dynamic_stack_size[$1] = true + when "static" + $self_stack_size[$1] = [$2.to_i, ($self_stack_size[$1] || 0)].max + else + abort("Unsupported stack usage type #{$3} in #{filename}\n") + end + end + } +} + +if $self_stack_size.size == 0 + abort("Error: could not load anything from *.su files in 'spl' directory.\n" + + "Please ensure that you are using '-fstack-usage' GCC option.\n") +end + +############################################################################### +# Parse the objdump log # +############################################################################### + +$detected_cycles = {} +$labels = {} +$arrows = {} +$functions = {} +$func_code = {} +$datastructs = {} +$refs = {} +$xrefs = {} +$max_stack_size = 0 +$critical_path = {} +$shortest_cycle = nil +$indirect_targets = {} +$indirect_sources = {} +$blue_arrows = {} + +# Stack usage, based on inspecting disassembly listing in objdump log +def guess_func_stack_usage(func_name) + func_code = $func_code[func_name] + if !func_code + printf("No function code for '%s'\n", func_name) + return nil + end + stack_usage = 0 + func_code.each_line { |l| + # 'push {reglist}' instruction + if l =~ /push\s+\{\s*(.*?)\s*\}/ + reglist = $1.split(",") + abort("Register ranges are not supported") if $1 =~ /\-/ + stack_usage += reglist.size * 4 + end + # 'sub sp, sp, #imm' instruction + if l =~ /sub\s+r13\s*\,\s*(r13\s*)\,\s*\#(\d+)/ + stack_usage += $2.to_i + end + } + return stack_usage +end + +# Stack usage, based on parsing *.su files +def get_func_stack_usage(func_name) + stack_usage = $self_stack_size[func_name] + stack_usage = guess_func_stack_usage(func_name) if !stack_usage + return stack_usage +end + +$objdump_sym_log.each_line {|l| + # 000034c8 l F .text 000000c8 twsi_i2c_read + next unless l =~ /^(\h+)\s+\S+\s+(\S+)\s+(\.\S+)\s+\h+.*?\s+(\S+)\s*$/ + addr = $1 + type = $2 + sect = $3 + name = $4 + if type == "F" && sect == ".text" + $functions[name] = addr.to_i(16) + else + $datastructs[name] = addr.to_i(16) + end +} + +$functions.each { |func, _| + if $dynamic_stack_size.has_key?(func) + abort("dynamic stack size in #{func}") + end +} + +Find.find("spl") { |filename| + next unless filename =~ /.*\.o$/ + skipflag = false + current_section = "" + `#{$toolchain}objdump -r #{filename}`.each_line {|l| + if l =~ /RELOCATION RECORDS FOR/ + if l =~ /\[\.text\.(.*?)\]/ + skipflag = !$functions.has_key?($1) + elsif l =~ /\[\.(data|rodata)\.(.*?)\]/ + skipflag = !$datastructs.has_key?($2) + else + skipflag = false + end + end + + next if skipflag + + if l =~ /(\S+)\s+(\S+)\s+(\S+)/ + reltype = $2 + name = $3 + next if !$functions.has_key?(name) + next if reltype == "R_ARM_CALL" || reltype == "R_ARM_JUMP24" + next if reltype == "R_ARM_THM_CALL" || reltype == "R_ARM_THM_JUMP24" + $indirect_targets[name] = 1 + end + } +} + +def add_ref(current_func, func_name) + return if $removed_arrows.has_key?([current_func, func_name]) + + $arrows[[current_func, func_name]] = 1 + $refs[current_func] = {} if !$refs.has_key?(current_func) + $refs[func_name] = {} if !$refs.has_key?(func_name) + $refs[current_func][func_name] = true + + $xrefs[current_func] = {} if !$xrefs.has_key?(current_func) + $xrefs[func_name] = {} if !$xrefs.has_key?(func_name) + $xrefs[func_name][current_func] = true +end + +# First pass, collect local aliases +current_func = "entry" +current_label = "entry" +$label_to_func_mapping = {} +$objdump_dis_log.each_line {|l| + current_label = $1 if l =~ /^\h+\s+\<(.*?)>\:$/ + current_func = $1 if l =~ /^\h+\s+\<(.*?)>\:$/ && $functions.has_key?($1) + $label_to_func_mapping[current_label] = current_func +} + +current_func = "entry" +$objdump_dis_log.each_line {|l| + current_func = $1 if l =~ /^\h+\s+\<(.*?)>\:$/ && $functions.has_key?($1) + if l =~ /(.*?)\<(.*?)(\+0x\h+)?\>/ + target_func = $label_to_func_mapping[$2] + add_ref(current_func, target_func) if target_func != current_func + end + if l =~ /\s+blx?\s+r/ + $indirect_sources[current_func] = 1 + end + if l =~ /\s+bx?\s+r/ && !(l =~ /\s+bx?\s+r14/) + $indirect_sources[current_func] = 1 + end + $func_code[current_func] = "" unless $func_code.has_key?(current_func) + $func_code[current_func] << sprintf("%s\n", l.strip) +} + +############################################################################### +# Handle indirect calls # +############################################################################### + +$functions[""] = 1 +$self_stack_size[""] = 0 + +$multiplexed_indirect_sources = $indirect_sources.clone +$multiplexed_indirect_targets = $indirect_targets.clone + +# A function can't indirectly call itself (that would be a recursion) +$indirect_sources.each {|src, _| + $removed_arrows[[src, src]] = 1 +} +# A function can't indirectly call its caller (that would be a recursion) +$refs.each {|src, dst_list| + dst_list.each {|dst, _| + $removed_arrows[[dst, src]] = 1 + } +} + +def impossible_indirect_call(bad_src, bad_dst) + return unless $multiplexed_indirect_sources.has_key?(bad_src) + return unless $multiplexed_indirect_targets.has_key?(bad_dst) + + if $multiplexed_indirect_sources.size < $multiplexed_indirect_targets.size + $multiplexed_indirect_sources.delete(bad_src) + $multiplexed_indirect_targets.each {|dst, _| + next if dst == bad_dst + newarrow = [bad_src, dst] + if !($removed_arrows.has_key?(newarrow)) + add_ref(newarrow[0], newarrow[1]) + $blue_arrows[newarrow] = 1 + end + } + else + $multiplexed_indirect_targets.delete(bad_dst) + $multiplexed_indirect_sources.each {|src, _| + next if src == bad_src + newarrow = [src, bad_dst] + if !($removed_arrows.has_key?(newarrow)) + add_ref(newarrow[0], newarrow[1]) + $blue_arrows[newarrow] = 1 + end + } + end +end + +$removed_arrows.each {|k, _| + impossible_indirect_call(k[0], k[1]) +} + +$multiplexed_indirect_sources.each {|src, _| + newarrow = [src, ""] + add_ref(newarrow[0], newarrow[1]) + $blue_arrows[newarrow] = 1 +} + +$multiplexed_indirect_targets.each {|dst, _| + newarrow = ["", dst] + add_ref(newarrow[0], newarrow[1]) + $blue_arrows[newarrow] = 1 +} + +############################################################################### + +def recursive_walk(node, visited_nodes, depth, total_stack) + $total_stack_size[node] = [total_stack, ($total_stack_size[node] || 0)].max + if $total_stack_size[node] > $max_stack_size + $critical_path = visited_nodes.clone + $max_stack_size = $total_stack_size[node] + end + $refs[node].each {|next_node, _| + func_stack = get_func_stack_usage(next_node) + if visited_nodes.has_key?(next_node) then + # Find shortest cycle + tmp = visited_nodes.sort {|a, b| a[1] <=> b[1]}.map {|a| a[0]} + idx = tmp.find_index(next_node) + tmp = tmp.slice(idx, tmp.size - idx) + $shortest_cycle = tmp if !$shortest_cycle + $shortest_cycle = tmp if tmp.size < $shortest_cycle.size + $detected_cycles[[node, next_node]] = 1 + next + end + visited_nodes[next_node] = depth if next_node != "" + recursive_walk(next_node, visited_nodes, depth + 1, + total_stack + func_stack) + visited_nodes.delete(next_node) + } +end + +# Find the 'root' nodes and recursively traverse the graph starting from them +$xrefs.each {|func_name, nodes| + next if nodes.size != 0 + visited_nodes = {} + depth = 0 + visited_nodes[func_name] = depth + recursive_walk(func_name, visited_nodes, depth + 1, + get_func_stack_usage(func_name)) +} + +############################################################################### +# Generate .dot file for graphviz # +############################################################################### + +$legend = "" +$max_stack_size = $total_stack_size.map { |k, v| v }.max + +def format_path_arr(path_arr) + result = "" + (path_arr + [path_arr[0]]).each_cons(2) { |a| + normal_call = $refs[a[0]].has_key?(a[1]) && + !$blue_arrows.has_key?([a[0], a[1]]) + indirect_call = ($refs[a[0]].has_key?("") && + $refs[""].has_key?(a[1])) || + $blue_arrows.has_key?([a[0], a[1]]) + if normal_call + result << sprintf("%s > ", a[0]) + elsif indirect_call + result << sprintf("%s ? ", a[0]) + else + result << sprintf("%s ! ", a[0]) + end + } + return result + path_arr[0] +end + +if $size_log =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\h+)\s+spl\/u\-boot\-spl/ + text_plus_data_size = $1.to_i + $2.to_i + bss_size = $3.to_i + + $legend << sprintf("------------ u-boot SPL ------------\\l") + $legend << sprintf("text+data = %5d bytes\\l", text_plus_data_size) + $legend << sprintf("bss = %5d bytes\\l", bss_size) + $legend << sprintf("stack usage = %5d bytes\\l", $max_stack_size) +end + +if $detected_cycles.size != 0 + $legend << sprintf("--------------------------------------\\l") + $legend << sprintf("Warning, recursion suspected:\\l\\\"%s\\\"\\l", + format_path_arr($shortest_cycle)) + $legend << sprintf("\\lWhere:") + $legend << sprintf("\\l '>' - means normal function call") + $legend << sprintf("\\l '?' - indirect call (via a pointer)\\l") + $legend << sprintf("\\lBlue arrows highlight possible indirect calls") + $legend << sprintf("\\lgoing through a fake multiplexer node. Red") + $legend << sprintf("\\larrows highlight possible cases of recursion") + $legend << sprintf("\\lon the callgraph. Recursion is bad because it") + $legend << sprintf("\\lmakes stack usage estimation unpredictable.\\l") + $legend << sprintf("\\lThe recursion warnings can be eliminated by:") + $legend << sprintf("\\l 1) Fixing the SPL code if it is really broken.") + $legend << sprintf("\\l 2) Incorrectly guessed indirect calls can") + $legend << sprintf("\\l be suppressed by using one or more") + $legend << sprintf("\\l 'remove_arrow[caller,callee]' command") + $legend << sprintf("\\l line arguments passed to the script.\\l") + $legend << sprintf("\\lThe nodes on the execution path with maximized") + $legend << sprintf("\\lstack usage are orange colored.\\l") +end + +fh = File.open("spl-stackusage.dot", "w") + +$refs.each {|func_name, nodes| + $labels[func_name] = sprintf("%s\\n[total=%d, self=%d]", + func_name, + $total_stack_size[func_name], + get_func_stack_usage(func_name)) +} + +fh.printf("digraph graphname {\n") +fh.printf("rankdir=LR;\n") +fh.printf("node [shape=box,fontname=helvetica];\n") + +$total_stack_size.each { |k, v| + extra = "" + shape = "box" + shape = "octagon" if $indirect_sources.has_key?(k) + shape = "doubleoctagon" if $indirect_targets.has_key?(k) + if $critical_path.has_key?(k) + extra = ",style=filled,fillcolor=orange,group=main" + end + if k == "" + shape = "oval" + extra = ",style=filled,fillcolor=lightblue" + end + fh.printf("\"%s\" [shape=#{shape}#{extra}];\n", $labels[k]) +} + +$arrows.each {|k, v| + if $detected_cycles.has_key?(k) then + fh.printf("\"%s\" -> \"%s\" [color=\"red\",penwidth=3];\n", + $labels[k[0]], $labels[k[1]]) + elsif $blue_arrows.has_key?(k) then + fh.printf("\"%s\" -> \"%s\" [color=blue];\n", + $labels[k[0]], $labels[k[1]]) + else + fh.printf("\"%s\" -> \"%s\";\n", $labels[k[0]], $labels[k[1]]) + end +} +fh.printf("legend [shape=box,style=filled,fontname=monospace,fontsize=24," + + "color=\"lightgray\",label=\"#{$legend}\"];\n") +fh.printf("}\n") +fh.close + +printf("Execution path with maximized stack usage (%d bytes):\n%s\n", + $max_stack_size, + format_path_arr($critical_path.invert.sort.map { |a| a[1] })) + +`dot -Tpng -o spl-stackusage.png spl-stackusage.dot`