Search code examples
adagnat

Constraint_Error raised when using modular types not divisble by 8


I've encountered an issue where using modular types in Ada that are not divisible by the system's Storage_Unit ( as defined in the runtime's system.ads ) will raise a Constraint_Error at runtime when accessed. I originally encountered this issue working on an bare-metal system using a minimal runtime while trying to read 12bit values from a buffer by overlaying the 12bit array over the buffer in memory. Does anyone know why this is occurring?

The following minimal example illustrates the issue I'm encountering. I tested this using AdaCore's GNAT 2019, compiled with the included zfp runtime. Using the standard runtime does not reproduce the issue.

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
   ----------------------------------------------------------------------------
   --  Modular type with standard size divisible by 8.
   ----------------------------------------------------------------------------
   type Type_One is mod 2 ** 16;
   type Buffer_Type_One is array (1 .. 128) of Type_One;

   ----------------------------------------------------------------------------
   --  Modular type with non-base 8 size.
   ----------------------------------------------------------------------------
   type Type_Two is mod 2 ** 12;
   type Buffer_Type_Two is array (1 .. 128) of Type_Two;

   type Buffer is array (1 .. 256) of Character;

   ----------------------------------------------------------------------------
   --  Example buffer.
   ----------------------------------------------------------------------------
   Test_Buffer : Buffer := (others => ' ');
begin
   ----------------------------------------------------------------------------
   --  Will not raise an exception.
   ----------------------------------------------------------------------------
   Test_One :
      declare
         Buffer_One : Buffer_Type_One
         with Import,
         Convention => Ada,
         Address    => Test_Buffer'Address;
      begin
         Put_Line ("Testing type one");
         for I in Natural range 1 .. 16 loop
            Put_Line ("Test: " & Buffer_One (I)'Image);
         end loop;
      end Test_One;

   ----------------------------------------------------------------------------
   --  Will raise a constraint error at runtime.
   ----------------------------------------------------------------------------
   Test_Two :
      declare
         Buffer_Two : Buffer_Type_Two
         with Import,
         Convention => Ada,
         Address    => Test_Buffer'Address;
      begin
         Put_Line ("Testing type two");
         for I in Natural range 1 .. 16 loop
            Put_Line ("Test: " & Buffer_Two (I)'Image);
         end loop;
      exception
         when Constraint_Error =>
            Put_Line ("Constraint error encountered.");
      end Test_Two;

end Main;

Here is the project file I used to compile this example:

project Test is

   for Object_Dir use "obj";
   for Exec_Dir use "build";
   for Create_Missing_Dirs use "True";

   for Languages use ("Ada");

   package Builder is
      for Executable ("main.adb") use "test";
   end Builder;

   for Main use ("main.adb");

   package Compiler is
      for Default_Switches ("Ada") use (
        "-gnat2012",
        "-gnatwadehl",
        "-gnatVa",
        "-gnaty3abcdefhiklmnoprstux"
      );
   end Compiler;

   for Runtime ("Ada") use "zfp";
end Test;

I can't seem to find anything in the RM that would indicate why this would happen.

EDIT: Simon Wright below has figured out why this is happening. My naive understanding was that an instance of Buffer_Type_Two overlaid at the specified memory address would interpret the memory at this location as a sequence of 12bit values. It appears that this is not the case. It appears as though the compiler is rounding the size of the type up to 16bits, then raising a Constraint_Error when the 16bit value read from the array does not conform to the 12bit type.

If anyone can think of a better way to read a sequence of 12bit values from a location in memory in a sequential way I would greatly appreciate it, thank you.


Solution

  • With recent GNATs, you can achieve the behaviour you want by defining Buffer_Type_Two as

    type Buffer_Type_Two is array (1 .. 128) of Type_Two
      with Pack;
    

    ARM 13.2(9) warns that this may not do what you want for 13-bit values (recent GNATs do, though).

    An alternative would be

    type Buffer_Type_Two is array (1 .. 128) of Type_Two
      with Component_Size => 12;
    

    The results are

    ...
    Testing type two
    Test:  32
    Test:  514
    Test:  32
    Test:  514
    ...
    

    For 13 bits, with either approach,

    ...
    Testing type two
    Test:  32
    Test:  257
    Test:  2056
    Test:  64
    Test:  514
    Test:  4112
    Test:  128
    Test:  1028
    Test:  32
    ...
    

    HOWEVER, for an embedded target, you’ll need to use the -full- runtime system; for others, as pointed out by @egilhh above,

    ajxs.adb:14:09: packing of 12-bit components not supported by configuration