Constraint_Error raised when using modular types not divisble by 8
Asked Answered
T

3

6

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.

Thaumatrope answered 21/5, 2020 at 11:4 Comment(5)
The comments in your code claims that you set the size, but there's no aspect in the code to that effect. Have you tried to actually set the size?Haifa
Thanks for your response. Yes, I did try setting the size aspect manually. The next thing I tried was to make the array type aliased. Neither of these helped.Thaumatrope
Try the above (plus fixes) but with pragma Pack and the appropriate size aspect?Accessary
Regarding your edit: The culprit seems to be the zfp... Aspects Pack and Component_Size is not supported in zfp, at least not for Size => 12Haifa
It appears that you are expecting a specific representation for Buffer_Type_Two without any representation clauses. A compiler is free to choose any valid representation unless a specific representation is specified. It would be perfectly valid for a compiler to use 32 bits for the components of your array types. If, as appears to bne the case, your compiler does not support the representation you desire, then you will have to manually extract the bits you need to create a value of your type.Clotheshorse
D
5

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
Donalt answered 21/5, 2020 at 15:56 Comment(1)
Just looked in the standard RTS, there are units System.Pack_03 through to System.Pack_63, but not of course the easy ones (no System.Pack_8, for instance)! Guessing they need to be there for packed components of the indicated sizes.Donalt
D
3

Considering the compilation warnings, the code didn’t really deserve to work ...

31.          Buffer_One : Buffer_Type_One
32.            with Import,
33.              Convention => Ada,
34.              Address    => Test_Buffer'Address;
                 |
    >>> warning: specified address for "Buffer_One" may be inconsistent with alignment
    >>> warning: program execution may be erroneous (RM 13.3(27))
    >>> warning: alignment of "Buffer_One" is 2
    >>> warning: alignment of "Test_Buffer" is 1

and

49.          Buffer_Two : Buffer_Type_Two
50.            with Import,
51.              Convention => Ada,
52.              Address    => Test_Buffer'Address;
                 |
    >>> warning: specified address for "Buffer_Two" may be inconsistent with alignment
    >>> warning: program execution may be erroneous (RM 13.3(27))
    >>> warning: alignment of "Buffer_Two" is 2
    >>> warning: alignment of "Test_Buffer" is 1

but that’s not the problem, as it happens: Type_Two is mod 2**12, i.e. mod 4096, but the value in Buffer_Two(1) is 16#2020# (two space characters), which is 8224 in decimal.

"But why isn’t the stored value automatically masked to the 12 bits I asked for?" you say. For reasons of efficiency, "the size of an object is not necessarily the same as the size of the type of [the] object", GNAT RM 4.43, and GNAT expects the spare 4 bits at the top of the 16-bit word to be zero. On its own, a value of type Type_Two occupies (has ’Object_Size of) 16 bits. You could get the 12-bit size you want by including the Type_Two field in a record and specifying its layout, but that does add complication.

The problem wasn’t detected here without the -gnatVa (turn on all validity checking options).

Donalt answered 21/5, 2020 at 13:51 Comment(4)
In the project file source I posted in my question it shows that I already had -gnatVa in the compilation switches, in addition to just about every other switch that GNAT has for code quality. For whatever reason I'm not being given the same warning that you are. I don't think I understand your answer. In this example I'm trying to use Buffer_Two to view the binary data located in memory at Test_Buffer'Address as a sequence of 12bit values. I'm not sure why this isn't working as I expect.Thaumatrope
I built it both native (GNAT CE 2019, 2020) and arm-eabi (GCC 10.1.0), all with -gnatVa, and I get the same CE with all.Donalt
Regarding your edit: I suspected something like this might be happening. Thank you for pointing out where in the RM this behavior is described. I did some experimentation with the default runtime which, instead of raising an error at runtime, showed results consistent with what the RM describes. I did consider a record representation clause to access it, however I may need to access hundreds sequentially. FYI: This is for reading the allocation table in a FAT12 formatted disk.Thaumatrope
I think you've answered the question correctly. Thanks again for your help. Do you think it's worth changing the question to ask for solutions to the problem with a more appropriate title? I'm more than happy to select your answer as the correct one.Thaumatrope
U
2

The physical value of 16#2020# is not in the range of Type_Two so Constraint_Error is expected with -gnatVa.

GNAT CE 2020 raises Constraint_Error.

Uncommenting the exception/when part leads to further information:

raised CONSTRAINT_ERROR : main.adb:51 invalid data

Unchaste answered 21/5, 2020 at 14:33 Comment(2)
You're correct. The reason why I'm confused about this behavior is because I'm expecting the Buffer_Type_Two array to interpret the memory at the specified address as a sequential array of 12bit values. It appears that this is not what is happening. It looks like it interprets it as a 16bit value, then raises a constraint error when this value is not within the type's constraints of 0 .. 2^32-1.Thaumatrope
The physical value can be checked with the 'Valid attribute. The attribute is True when the physical value (the 16 bits in the cell) is a valid logical value (Type_Two).Unchaste

© 2022 - 2024 — McMap. All rights reserved.