5. Additional Capabilities

As indicated in The GNATstack Tool, there is a list of potential problems when computing stack requirements: indirect and external calls, unbounded frames, and recursion. This chapter provides guidance about how to address these issues.

The first possibility to address these issues is to avoid the occurrence of these events by means of using the appropriate restrictions and coding style rules. The second is to supply additional information that will help GNATstack to proceed with the analysis.

  • Indirect calls.

    Determining statically the target subprogram when dereferencing an access-to-subprogram variable is not always possible. These occurrences can be forbidden through pragma Restrictions. The use of No_Access_Subprograms avoids access-to-subprogram types. Dispatching calls can also be restricted by using No_Dispatch (which prohibits classwide constructs), or the less restricting No_Dispatching_Calls (which ensures that no dispatching calls are present).

    The -Wi option can be used to signal all the occurrences of indirect calls in the program under analysis.

  • External calls.

    These are calls to subprograms for which no stack usage or call graph information is available, introducing an unknown amount of stack usage in a call chain. It can only be avoided by providing the required information for the objects and libraries that make up the application. The -We option can be used to signal all the occurrences of external calls in the program under analysis.

  • Unbounded frames.

    The use of dynamically sized local objects (such as those whose size depends on an input argument) makes it impossible to statically bound stack usage. The -Wu option can be used to signal all the occurrences of unbounded frames in the program under analysis.

  • Recursion.

    Very simple cases can be detected by using pragma Restrictions (No_Recursion). This restriction ensures that as part of the execution of a subprogram the same subprogram is not invoked. More complex situations require call graph analysis. gnatcheck, see GNAT User’s Guide, can be used to detect potential recursion. GNATstack can also detect all the occurrences of cycles in the call graph using the options -ca -Wc.

If the occurrence of these potential problem cannot be avoided, GNATstack provides the means to supply additional stack usage and call graph information.

When GNATstack finds incomplete information, such as external and indirect calls, it generates a file (undefined.ciu) containing placeholders for the missing information. The format of this file is similar to the .ci files, but with XXX where the user information must be inserted.

For example, entries generated by GNATstack for external calls have the following pattern:

node: { title: "ext_proc" label: "Ext_Proc\\n/path/file.ads:1:2\\nXXX bytes" }

User-defined values can be specified in a .ci file by simply replacing the XXX by the estimated value.

For indirect calls, the undefined.ciu file contains entries like:

edge: { sourcename: "source" targetname: "XXX" label: "file.adb:6:15" }

In this case, the list of potential targets for the indirect call can be placed in a user-defined .ci file, replacing the XXX by the symbol name of the potential target of the call:

edge: { sourcename: "source" targetname: "target1" label: "file.adb:6:15" }
edge: { sourcename: "source" targetname: "target2" label: "file.adb:6:15" }
edge: { sourcename: "source" targetname: "target3" label: "file.adb:6:15" }

The use of label with the concrete location indicates that the targets specified are only for the indirect call at that location. If no location is put then the user-defined call will be used for every indirect call from source.

If there is an unbounded frame for which a maximal size can be derived by some other kind of analysis, this information can be manually added to a user-defined .ci file. In this case, GNATstack will take the user-defined (more concrete) information, ignoring the compiler-generated information. The entry in the .ci would have the following pattern:

node: { title: "dyn_proc" label: "XXX bytes (static)" }

where XXX needs to be replaced by the stack size obtained by some other analysis.

The subprogram names specified in these user-defined .ci files are the symbolic names used by the compiler rather than simple Ada identifiers. These symbolic names are not identical to the Ada identifiers.

The most typical scenario is that of subprograms declared in packages. In this case, the names are specified with the fully qualified name, but in lower case and with two consecutive underscores replacing the dots in the full name. For example, given the following package:

package P is
   procedure R;
   procedure Q (This : Integer);
   procedure Q (This : Float);
end P;

The fully qualified names in Ada would be P.R and P.Q but the names specified in a user-defined .ci file would be p__r and p__q.

Additional characters make up the symbolic names for overloaded units. Specifically, unique numbers are appended to the symbolic names by the compiler, again using double underscore characters as separators. The first overloaded unit does not get a numeric suffix, but all others in that scope do, starting at two. Hence the corresponding symbolic names specified would be p__q and p__q__2, respectively.

For subprograms declared in scopes other than packages, such as subprograms nested within other subprograms, a similar format is used as for overloaded units. An additional numeric suffix is added by the compiler, but in this case the separator is a leading dot instead of two underscore characters. Moreover, these numeric suffixes are not deducible simply by counting the overloaded units in the scope. The most convenient approach for determining the full symbolic name is to look in the .ci files generated by the compiler.

Consider the following:

procedure Demo is

   type Index is new Integer;

   procedure Silly (Upper : Index) is
      Data_List : array (1 .. Upper) of Integer;
   begin
      for Value of Data_List loop
         Value := 0;
      end loop;
   end Silly;

begin
   Silly (Upper => 1024);
end Demo;

We need to specify the stack requirements for procedure Silly. The full Ada name is Demo.Silly, so the symbolic name starts with demo__silly but there will be a numeric suffix as well.

If we look in the demo.ci file we will see the following:

graph: {title: "demo.adb"
node: {title: "_ada_demo" label: "Demo\\ndemo.adb:1:1\\n16 bytes (static)\\n0
dynamic objects" }
edge: {sourcename: "_ada_demo" targetname: "demo.adb:demo__silly.1435"
label: "demo.adb:14:4" }
node: {title: "demo.adb:demo__silly.1435" label:"Silly\\ndemo.adb:5:4\\n80 bytes
(dynamic)\\n1 dynamic objects\\n Data_List demo.adb:6:7" }
}

We can see in the last node that the symbolic name for Demo.Silly is demo__silly.1435 and that is what would be specified in the user-defined .ci file:

node: { title: "demo__silly.1435" label: "4128 bytes (static)" }

Note that we have used an arbitrary number (4128) of bytes in this user-defined value.

Alternatively, the symbolic names chosen by the compiler appear in the object code so we can use objdump or nm to show the symbols within:

$powerpc-elf-objdump --syms demo.o

demo.o:     file format elf32-powerpc

SYMBOL TABLE:
00000000 l    df *ABS*  00000000 demo.adb
00000000 l    d  .text  00000000 .text
00000000 l    d  .data  00000000 .data
00000000 l    d  .bss   00000000 .bss
00000000 l     F .text  00000120 demo__silly.1435
00000000 l    d  .debug_info    00000000 .debug_info
00000000 l    d  .debug_abbrev  00000000 .debug_abbrev
...

As before, we can see that the symbolic name for Demo.Silly is demo__silly.1435.

When there is some missing information the result of the static stack analysis is not accurate. This situation is indicated by GNATstack when displaying the results with the following warning message:

Worst case analysis is *not* accurate because of ...; use -Wa for details

In addition GNATstack provides accuracy information on a per-subprogram basis. The mark * is attached to the subprograms when there is any missing information.

The accumulated worst-case stack usage for the different entry points will contain the mark +? to indicate that extra stack utilization may happen:

$ gnatstack *.ci
[...]
Worst case analysis is *not* accurate because of cycles, external calls.
Use -Wa for details.

Accumulated stack usage information for entry points

main : total 504+? bytes
 +-> main
 +-> proc_a *
 +-> proc_b *