Optimization and Strict Aliasing (GNAT User’s Guide for Native Platforms)

#### 6.3.1.9 Optimization and Strict Aliasing ¶

The strong typing capabilities of Ada allow an optimizer to generate efficient code in situations where other languages would be forced to make worst case assumptions preventing such optimizations. Consider the following example:

>     procedure M is
>        type Int1 is new Integer;
>        I1 : Int1;

>        type Int2 is new Integer;
>        type A2 is access Int2;
>        V2 : A2;
>        ...

>     begin
>        ...
>        for J in Data'Range loop
>           if Data (J) = I1 then
>              V2.all := V2.all + 1;
>           end if;
>        end loop;
>        ...
>     end;

Here, since `V2` can only access objects of type `Int2` and `I1` is not one of them, there is no possibility that the assignment to `V2.all` affects the value of `I1`. This means that the compiler optimizer can infer that the value `I1` is constant for all iterations of the loop and load it from memory only once, before entering the loop, instead of in every iteration (this is called load hoisting).

This kind of optimizations, based on strict type-based aliasing, is triggered by specifying an optimization level of `-O2` or higher (or `-Os`) for the GCC back end and `-O1` or higher for the LLVM back end and allows the compiler to generate more efficient code.

However, although this optimization is always correct in terms of the formal semantics of the Ada Reference Manual, you can run into difficulties arise if you use features like `Unchecked_Conversion` to break the typing system. Consider the following complete program example:

>     package P1 is
>        type Int1 is new Integer;
>        type A1 is access Int1;

>        type Int2 is new Integer;
>        type A2 is access Int2;
>     end P1;

>     with P1; use P1;
>     package P2 is
>        function To_A2 (Input : A1) return A2;
>     end p2;

>     with Ada.Unchecked_Conversion;
>     package body P2 is
>        function To_A2 (Input : A1) return A2 is
>           function Conv is
>             new Ada.Unchecked_Conversion (A1, A2);
>        begin
>           return Conv (Input);
>        end To_A2;
>     end P2;

>     with P1; use P1;
>     with P2; use P2;
>     with Text_IO; use Text_IO;
>     procedure M is
>        V1 : A1 := new Int1;
>        V2 : A2 := To_A2 (V1);
>     begin
>        V1.all := 1;
>        V2.all := 0;
>        Put_Line (Int1'Image (V1.all));
>     end;

This program prints out `0` in `-O0` mode, but it prints out `1` in `-O2` mode. That’s because in strict aliasing mode, the compiler may and does assume that the assignment to `V2.all` could not affect the value of `V1.all`, since different types are involved.

This behavior is not a case of non-conformance with the standard, since the Ada RM specifies that an unchecked conversion where the resulting bit pattern is not a correct value of the target type can result in an abnormal value and attempting to reference an abnormal value makes the execution of a program erroneous. That’s the case here since the result does not point to an object of type `Int2`. This means that the effect is entirely unpredictable.

However, although that explanation may satisfy a language lawyer, in practice, you probably expect an unchecked conversion involving pointers to create true aliases and the behavior of printing `1` is questionable. In this case, the strict type-based aliasing optimizations are clearly unwelcome.

Indeed, the compiler recognizes this possibility and the instantiation of Unchecked_Conversion generates a warning:

>     p2.adb:5:07: warning: possible aliasing problem with type "A2"
>     p2.adb:5:07: warning: use -fno-strict-aliasing switch for references
>     p2.adb:5:07: warning:  or use "pragma No_Strict_Aliasing (A2);"

Unfortunately the problem is only recognized when compiling the body of package `P2`, but the actual problematic code is generated while compiling the body of `M` and this latter compilation does not see the suspicious instance of `Unchecked_Conversion`.

As implied by the warning message, there are approaches you can use to avoid the unwanted strict aliasing optimizations in a case like this.

One possibility is to simply avoid the use of higher levels of optimization, but that is quite drastic, since it throws away a number of useful optimizations that don’t involve strict aliasing assumptions.

A less drastic approach is for you to compile the program using the `-fno-strict-aliasing` switch. Actually, it is only the unit containing the dereferencing of the suspicious pointer that you need to compile with that switch. So, in this case, if you compile unit `M` with this switch, you get the expected value of `0` printed. Analyzing which units might need the switch can be painful, so you may find it a more reasonable approach is to compile the entire program with options `-O2` and `-fno-strict-aliasing`. If you obtain satisfactory performance with this combination of options, then the advantage is that you have avoided the entire issue of possible problematic optimizations due to strict aliasing.

To avoid the use of compiler switches, you may use the configuration pragma `No_Strict_Aliasing` with no parameters to specify that for all access types, the strict aliasing optimizations should be suppressed.

However, these approaches are still overkill, in that they cause all manipulations of all access values to be deoptimized. A more refined approach is to concentrate attention on the specific access type identified as problematic.

The first possibility is to move the instantiation of unchecked conversion to the unit in which the type is declared. In this example, you would move the instantiation of `Unchecked_Conversion` from the body of package `P2` to the spec of package `P1`. Now, the warning disappears because any use of the access type knows there is a suspicious unchecked conversion and the strict aliasing optimizations are automatically suppressed for it.

If it’s not practical to move the unchecked conversion to the same unit in which the destination access type is declared (perhaps because the source type is not visible in that unit), the second possibility is for you to use pragma `No_Strict_Aliasing` for the type. You must place this pragma in the same declarative part as the declaration of the access type:

>     type A2 is access Int2;
>     pragma No_Strict_Aliasing (A2);

Here again, the compiler now knows that strict aliasing optimizations should be suppressed for any dereference made through type `A2` and the expected behavior is obtained.

The third possibility is to declare that one of the designated types involved, namely `Int1` or `Int2`, is allowed to alias any other type in the universe, by using pragma `Universal_Aliasing`:

>     type Int2 is new Integer;
>     pragma Universal_Aliasing (Int2);

The effect is equivalent to applying pragma `No_Strict_Aliasing` to every access type designating `Int2`, in particular `A2`, and, more generally, to every reference made to an object of declared type `Int2`, so it’s very powerful and effectively takes `Int2` out of the alias analysis performed by the compiler in all circumstances.

You can also use this pragma used to deal with aliasing issues that arise from the use of `Unchecked_Conversion` in the source code but without the presence of access types. The typical example is code that streams data by means of arrays of storage units (bytes):

>     type Byte is mod 2**System.Storage_Unit;
>     for Byte'Size use System.Storage_Unit;

>     type Chunk_Of_Bytes is array (1 .. 64) of Byte;

>     procedure Send (S : Chunk_Of_Bytes);

>     type Rec is record
>        ...
>     end record;

>     procedure Dump (R : Rec) is
>        function To_Stream is
>           new Ada.Unchecked_Conversion (Rec, Chunk_Of_Bytes);
>     begin
>        Send (To_Stream (R));
>     end;

This generates the following warning for the call to `Send`:

>     dump.adb:8:25: warning: unchecked conversion implemented by copy
>     dump.adb:8:25: warning: use pragma Universal_Aliasing on either type
>     dump.adb:8:25: warning: to enable RM 13.9(12) implementation permission

This occurs because the formal parameter `S` of `Send` is passed by reference by the compiler and it’s not possible to pass a reference to `R` directly in the call without violating strict type-based aliasing. That’s why the compiler generates a temporary of type `Chunk_Of_Bytes` just before the call and passes a reference to this temporary instead.

As implied by the warning message, you can avoid the temporary (and the warning) by means of pragma `Universal_Aliasing`:

>     type Chunk_Of_Bytes is array (1 .. 64) of Byte;
>     pragma Universal_Aliasing (Chunk_Of_Bytes);

You can also apply this pragma to the component type instead:

>     type Byte is mod 2**System.Storage_Unit;
>     for Byte'Size use System.Storage_Unit;
>     pragma Universal_Aliasing (Byte);

and every array type whose component is `Byte` will inherit the pragma.

To summarize, the alias analysis performed in strict aliasing mode by the compiler can have significant benefits. We’ve seen cases of large scale application code where the execution time is increased by up to 5% when these optimizations are turned off. However, if you have code that make significant use of unchecked conversion, you might want to just stick with `-O1` (with the GCC back end) and avoid the entire issue. If you get adequate performance at this level of optimization, that’s probably the safest approach. If tests show that you really need higher levels of optimization, then you can experiment with `-O2` and `-O2 -fno-strict-aliasing` to see how much effect this has on size and speed of the code. If you really need to use `-O2` with strict aliasing in effect, then you should review any uses of unchecked conversion, particularly if you are getting the warnings described above.