7. 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.
The same example is built and analyzed in the same way with an LLVM based toolchain. The project file names the two switches that toolchain needs and asks for a static link:
project Prj is
for Main use ("main_unit.adb");
for Object_Dir use "obj";
package Compiler is
for Default_Switches ("Ada") use
("-fexperimental-call-graph-section", "-fstack-size-section");
end Compiler;
package Linker is
for Default_Switches ("Ada") use ("-static");
end Linker;
package Stack is
for Switches use ("-p");
end Stack;
end Prj;
The linked executable is decoded before the analysis, and the resulting
.ci file is passed to gnatstack explicitly, as explained in
LLVM based toolchains:
$ gprbuild -P prj.gpr
[...]
$ llvm-callgraph-decoder obj/main_unit --ci > main_unit.ci
$ gnatstack -P prj.gpr main_unit.ci
Worst case analysis is *not* accurate because of external calls. Use -Wa for details.
Accumulated stack usage information for entry points
main : total 112 bytes
+-> main
+-> main_unit
+-> main_unit.inverse
The worst-case path found is the same one. The total differs from the figure above because the two compilers do not allocate the same frames for this program. The warning concerns external calls elsewhere in the program: the path shown reaches none of them, which is why its total carries no “+?”.
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 the following. This one is a GCC example, because it relies on the da marker, which an LLVM based toolchain does not provide:
$ 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 requirement 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 are 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.