6. ExamplesΒΆ
This section contains a number of examples of the use of GNATstack showing how to use the tool and how to interpret the results. Let us start analyzing a simple program like the following:
procedure Main_Unit is
type Data_Type is array (1 .. 5) of Integer;
function Inverse (Input : Data_Type) return Data_Type is
Result : Data_Type;
begin
for Index in Data_Type'Range loop
Result (Index) := Input (Data_Type'Last -
(Index - Data_Type'First));
end loop;
return Result;
end Inverse;
Data : Data_Type := (1, 2, 3, 4, 5);
Result : Data_Type;
begin
Result := Inverse (Data);
end Main_Unit;
A typical project file would contain the flags to compile the application with the required options to generate stack consumption and call-graph information. In addition, it would contain the desired option for the stack analysis phase:
project Prj is
for Main use ("main_unit.adb");
package Compiler is
for Default_Switches ("Ada") use ("-fcallgraph-info=su");
end Compiler;
package Stack is
for Switches use ("-p");
end Stack;
end Prj;
We can then easily compile and analyze the application using this project file:
$ gprbuild -P prj.gpr
[...]
$ gnatstack -P prj.gpr
Accumulated stack usage information for entry points
main : total 376 bytes
+-> main
+-> main_unit
+-> main_unit.inverse
Note that we do not need to specify the .ci
files for the
application. gnatstack computes automatically this list from
the project file. In addition, the main entry point has also been automatically
detected by GNATstack.
The execution of the tool tells that the longest path (in terms of stack consumption) is that made up by main (the program entry point) -> main_unit (the main Ada program) -> main_unit.inverse. The maximum stack consumption following this path is 376 bytes, and this bound can be trusted.
Focusing on the capability of detecting problems related to the computation of stack requirements we can use the following example:
package Ext is
procedure Set_Alignment (Minimum : Integer);
function Get_Size (Value : Integer) return Integer;
end Ext;
package body Ext is
Threshold : Integer;
procedure Set_Alignment (Minimum : Integer) is
begin
Threshold := Minimum;
end Set_Alignment;
function Get_Size (Value : Integer) return Integer is
begin
return (((Value - 1) / Threshold) + 1) * Threshold;
end Get_Size;
end Ext;
with Ext;
-- Ext is compiled without stack usage information
procedure P is
Set_Alignment : access procedure (Minimum : Integer) :=
Ext.Set_Alignment'Access;
-- Indirect call
procedure R;
procedure Q is
Data : array (1 .. Ext.Get_Size (10)) of Character;
-- Dynamically sized local objects
begin
R;
end Q;
procedure R is
begin
Q;
end R;
begin
-- Set minimum alignment (via indirect call)
Set_Alignment.all (Integer'Alignment);
-- Recursion: Q -> R -> Q
Q;
end P;
To compile and analyze this program we do:
$ gcc -c ext.adb
$ gprbuild p.adb -cargs -fcallgraph-info=su,da
[...]
$ gnatstack *.ci
[...]
Worst case analysis is *not* accurate because of cycles, unbounded frames,
external calls, indirect calls. Use -Wa for details.
Accumulated stack usage information for entry points
main : total 472+? bytes
+-> main
+-> p
+-> p.q *
+-> p.r *
The result of the analysis is not accurate. As indicated in the warning message, we can check which is the origin of this inaccuracy using the -W option. Additional information can be extracted increasing the verbosity level. Adding the -v option will also print detailed information about the location of the different subprograms and per-subprogram stack requirements:
$ gnatstack -Wa -v -p *.ci
[...]
List of reachable cycles:
<c1> p.q
+-> p.q at Q:p.adb:11:14 : 144+? bytes (unbounded,cycle) (<c1>)
+-> p.r at R:p.adb:9:14 : 104+? bytes (cycle) (<c1>)
+-> p.q at Q:p.adb:11:14 : 144+? bytes (unbounded,cycle) (<c1>)
List of reachable subprograms with dynamic unbounded frames:
In p.q at Q:p.adb:11:14
Data at p.adb:12
List of reachable external subprograms:
ext.get_size at Get_Size:ext.ads:3:13
List of indirect (including dispatching) calls:
1 indirect calls in: p at P:p.adb:4:11
at p.adb:24
Accumulated stack usage information for entry points
main : total 472+? bytes (unbounded,cycle)
+-> main at main:b~p.ads:24:14 : 112 bytes
+-> p at ada_main_program:b~p.adb:28:17,P:p.adb:4:11 : 112 bytes
+-> p.q at Q:p.adb:11:14 : 144+? bytes (unbounded,cycle) (<c1>)
+-> p.r at R:p.adb:9:14 : 104+? bytes (cycle) (<c1>)
We can see that the result is not accurate because there is recursion, dynamic frames, and symbols for which there is no stack usage information.
GNATstack has detected one cycle in the call graph (Q calling R which in turn calls Q). The cycle has been tagged as <c1> for later references. The following section of the report indicates that the use of a dynamically sized local object (Data) derives into a dynamic unbounded frame (subprogram Q). We then have the list of subprograms for which there is no stack usage information (package Ext has been compiled without the required flags). Finally, there is a list of locations where there is an indirect call.
Looking at the information about entry points we can see that the computed stack requirements for the main entry point is 472 bytes. However, this result is not accurate (as indicated by the mark +?), and we see that the two problems that have been found is the existence of cycles and unbounded frames.
The call chain information provides more detailed information about the subprograms that are part of a cycle (Q and R in this case), and the dynamic size of the frame for subprogram Q.