2

I tried to convert the array concatenation in function A to the new Ada 2022 reduction expression (see function B), but I get a range check failed error. What am I doing wrong?

    with Ada.Text_IO;
    with Ada.Unchecked_Conversion;
    with Interfaces;
    
    procedure Learn is
      type u8 is new Interfaces.Unsigned_8;
      type u32 is new Interfaces.Unsigned_32;
      
      type Bytes is array (Positive range <>) of aliased u8;
      type Words is array (Positive range <>) of aliased u32;
      
      Some_Words : Words (1 .. 3) := [1111, 22222, 333333];
      
      subtype Bytes_4 is Bytes (1 .. 4);
      function Convert is new Ada.Unchecked_Conversion (u32, Bytes_4);
      
      function A (W : Words) return Bytes is
        (Convert (W (1)) & Convert (W (2)) & Convert (W (3)));
      A1 : Bytes := A (Some_Words);
      
      function B (W : Words) return Bytes is
        ([for X of W => Convert (X)]'Reduce ("&", []));
      B1 : Bytes := B (Some_Words);
    begin
        Ada.Text_IO.Put_Line (A1'Image);
        Ada.Text_IO.Put_Line (B1'Image);
    end Learn;

Console output:

$ gprbuild -q -P main.gpr
learn.adb:22:30: warning: too many elements for subtype of "Bytes" defined at line 22 [enabled by default]
learn.adb:22:30: warning: expected 0 elements; found 4 elements [enabled by default]
learn.adb:22:30: warning: Constraint_Error will be raised at run time [enabled by default]
Build completed successfully.
$ ./learn
raised CONSTRAINT_ERROR : learn.adb:22 range check failed
exit status: 1
2
  • I get the same error in GCC 13.2.0. It might be worth filing a bug report at the GCC bugzilla page Commented Aug 8, 2024 at 13:21
  • Also present in v14.1.0. Commented Aug 16, 2024 at 22:13

2 Answers 2

4

I’m not sure this is a bug. It could be an (overly?) simple interpretation of the ARM.

The learn.adacore section on reduction expressions says, a little way down, that

A := [2, 3, 4];
I := A'Reduce ("+", 0);

is equivalent to

I := 0;
for E of A loop
   I := I + E;
end loop;

and you can see that in your case you initialize the accumulator (a local variable equivalent to I above) to [], with fixed length 0.

Indeed, using the -gnatG switch, we get this indication of the compiler's internal representation of your function B:

   function b (w : words) return bytes is
   begin
      null;
      return
         do
            [subtype T14b is bytes (1 .. 0)]
            B37b : T14b := [];
            L39b : for C40b in w'first(1) .. w'last(1) loop
               [constraint_error when
                 not (integer(C40b) in w'first .. w'last)
                 "index check failed"]
               x : u32 renames w (C40b);
               B37b := [constraint_error "range check failed"];
            end loop L39b;
         in B37b end
      ....
   end learn__b;

(B37b is the accumulator, and the assignment raises an unconditional CE).

The ARM says in ARM 4.5.10(24) that "The evaluation of a use of this attribute ... initializes the accumulator of the reduction expression to the ... initial value". So far, so good (or bad, depending on your point of view).

The next paragraph says that "each value of the value_sequence is passed, in order, as the second (Value) parameter to a call on Reducer, with the first (Accumulator) parameter being the prior value of the accumulator, saving the result as the new value of the accumulator". I think you’d need to use recursion when the required result type is unconstrained, and I very much doubt that the ARG would require an implementer to do that.

Sign up to request clarification or add additional context in comments.

Comments

3

As shown here, @Simon Wright elucidates how the error arises. The dynamic semantics of Reduction Expressions outlines how a single accumulator is updated, while the Binary Adding Operator to concatenate two arrays is "defined in terms of an assignment to an anonymous object" as for a function call. This use of "&" may not be supported.

In the alternative, consider Custom reducers such as Other accumulator types, which illustrate reducer implementations using a record type or Container instance.

As a concrete example, this variation of your program accumulates bytes in a simple Buffer.

-- https://stackoverflow.com/q/78839201/230513
with Ada.Text_IO;
with Ada.Unchecked_Conversion;
with Interfaces;

procedure Reduction is
   type u8 is new Interfaces.Unsigned_8;
   type u32 is new Interfaces.Unsigned_32;
   type Bytes is array (Positive range <>) of aliased u8;
   type Words is array (Positive range <>) of aliased u32;

   V1234      : constant u32            := 2**24 + 2**16 * 2 + 2**8 * 3 + 4;
   Some_Words : constant Words (1 .. 3) := [others => V1234];

   subtype Bytes_4 is Bytes (1 .. 4);
   function Convert is new Ada.Unchecked_Conversion (u32, Bytes_4);

   function A (W : Words) return Bytes is
     (Convert (W (1)) & Convert (W (2)) & Convert (W (3)));
   A1 : constant Bytes := A (Some_Words);

   subtype Buffer_Bytes is Bytes (1 .. Bytes_4'Length * Some_Words'Length);
   type Buffer is record
      Index : Positive     := 1;
      Value : Buffer_Bytes := [others => 0];
   end record;
   Some_Bytes : Buffer;

   procedure Reducer (Accumulator : in out Buffer; Value : Bytes_4) is
   begin
      for E of Value loop
         Accumulator.Value (Accumulator.Index) := E;
         Accumulator.Index := Accumulator.Index + 1;
      end loop;
   end Reducer;

   function B (W : Words) return Buffer is
     ([for X of W => Convert (X)]'Reduce (Reducer, Some_Bytes));
   B1 : constant Buffer := B (Some_Words);

begin
   Ada.Text_IO.Put_Line (A1'Image);
   Ada.Text_IO.Put_Line (B1.Value'Image);
end Reduction;

Console:

[ 4,  3,  2,  1,  4,  3,  2,  1,  4,  3,  2,  1]
[ 4,  3,  2,  1,  4,  3,  2,  1,  4,  3,  2,  1]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.