Help wanted with compilation error

I can’t see what I’m doing wrong here at line 41. Constant aggregates are best, but … is it something to do with array aggregates?

This will, I hope, be code for a RISC-V core in an RP2350 chip.

-*- mode: compilation; default-directory: "~/Developer/FreeRTOS-Ada/rp2350/adainclude/" -*-
Compilation started at Sat Sep  7 20:10:13

gnatmake problem.adb -gnatl
gcc -c -gnatl problem.adb

GNAT 14.2.0
Copyright 1992-2024, Free Software Foundation, Inc.


Compiling: problem.adb
Source file time stamp: 2024-09-07 19:09:06
Compiled at: 2024-09-07 20:10:13

     1. with System.Machine_Code;
     2.
     3. procedure Problem is
     4.
     5.    package RP2350 is
     6.       type Bit is mod 2**1
     7.       with Size => 1;
     8.       type UInt5 is mod 2**5
     9.       with Size => 5;
    10.       type UInt11 is mod 2**11
    11.       with Size => 11;
    12.    end RP2350;
    13.    use RP2350;
    14.
    15.    type Interrupt_ID is range 0 .. 45;
    16.
    17.    procedure Enable_Interrupt (ID : Interrupt_ID);
    18.
    19.    --  This type is subtyped for MEIEA, MEIPA, MEIFA.
    20.
    21.    type Bit_Window_Content is array (0 .. 15) of Boolean
    22.    with Pack, Size => 16;
    23.    type CSR_Bit_Data is record
    24.       INDEX    : RP2350.UInt5   := 0;
    25.       Reserved : RP2350.UInt11  := 0;
    26.       WINDOW   : Bit_Window_Content := (others => False);
    27.    end record
    28.    with Size => 32;
    29.
    30.    for CSR_Bit_Data use record
    31.       INDEX    at 0 range 0 .. 4;
    32.       Reserved at 0 range 5 .. 15;
    33.       WINDOW   at 0 range 16 .. 31;
    34.    end record;
    35.
    36.    subtype MEIEA_Data is CSR_Bit_Data;
    37.
    38.    procedure Enable_Interrupt (ID : Interrupt_ID)
    39.    is
    40.       ID_In_Window : constant Integer := Integer (ID mod 16);
    41.       Window : constant Bit_Window_Content := (ID_In_Window => True,
                                                       |
        >>> error: dynamic or empty choice in aggregate must be the only choice

    42.                                                others => False);
    43.       Data : constant MEIEA_Data := (INDEX => UInt5 (ID / 16),
    44.                                      WINDOW => Window,
    45.                                      others => <>);
    46.    begin
    47.       System.Machine_Code.Asm
    48.         ("csrrs meinext, %0",
    49.          Inputs  => MEIEA_Data'Asm_Input
    50.            ("r", Data),
    51.          Volatile => True);
    52.    end Enable_Interrupt;
    53.
    54. begin
    55.    null;
    56. end Problem;

 56 lines: 1 error
gnatmake: "problem.adb" compilation error

Compilation exited abnormally with code 4 at Sat Sep  7 20:10:13

I think it’s choking on the fact that Integer can be outside 0..15, even though you have that taken care of with the mod 16.

Could you quickly add/alter so that:

   type Nybble is range 0..15 with Size => 4;
   type Bit_Window_Content is array ( Nybble ) of Boolean
        with Pack, Size => 16;
--…
procedure Enable_Interrupt (ID : Interrupt_ID) is
   ID_In_Window : constant Nybble := Nybble (ID mod 16);
   Window : constant Bit_Window_Content := (ID_In_Window => True, others => False);
--…

Value of the ID_In_Window doesn’t known at compilation time.

Have you tried this ?

Window : constant Bit_Window_Content := Bit_Window_Content'(ID_In_Window => True, others => False);

That shouldn’t matter.

Declare
   Text : Constant String:= Ada.Text_IO.Get_Line;
Begin
   --...
End;

This is perfectly valid… so it shouldn’t be that there is runtime/unknown indefiniteness, in and of itself.

Hm, qualification is a good idea; try:

procedure Enable_Interrupt (ID : Interrupt_ID) is
   ID_In_Window : Nybble renames Nybble(ID mod 16);
   Window : Bit_Window_Content renames 
        Bit_Window_Content'(ID_In_Window => True, others => False);

Many suggestions, still puzzling!

    40.       ID_In_Window : constant Integer := Integer (ID mod 16);
    41.       Window : constant Bit_Window_Content
    42.         := Bit_Window_Content'(ID_In_Window => True,
                                       |
        >>> error: dynamic or empty choice in aggregate must be the only choice

    43.                                others => False);

I tried removing the constant … no joy.

This goes against the grain, but compiles OK, so I’ll leave determining whether there’s an error to be reported to another day! Thanks for all the suggestions.

      Data : MEIEA_Data;
   begin
      Data.INDEX := RP2350.UInt5 (ID / 16);
      Data.WINDOW (Integer (ID mod 16)) := True;

Of course, given the very short scope of this procedure, the generated code may well be just the same as would have been produced by the first try had it worked.

I think the way this hardware works (it’s RaspberryPi’s Xh3irq interrupt controller extension in the RISC-V half of the RP2350) needs a little bit more explanation by way of comments in the code.

Oh, and the CSR should have been meiea. I suppose you have to expect assembler mnemonics to be obscure.

You might try moving it after begin and add a declare block for Data. It would have to lose the constant property though.

Will this work as a work-around?:

Window : constant Bit_Window_Content := 
        (for I in 0 .. 15 => (if I = ID_In_Window then True else False));

I tried it in jdoodle and got it to compile on GNAT 13.2.

I don’t know if it is a bug or intended. I looked at section 4.3.3 of the RM (ref below) but coudln’t make a solid decision on if your code is allowed or not.

jdoodle example: Online Compiler and Editor/IDE for Java, C, C++, PHP, Python, Ruby, Perl - Code and Run Online

RM Section: Array Aggregates

1 Like

I fixed that for you.

I think I prefer the more wordy way in this case. Helps me see everything better with how the words are colored.

1 Like

This is required by the language; see ARM 4.3.3(17/3):

The discrete_choice_list of an array_component_association is allowed to have a discrete_choice that is a nonstatic choice_expression or that is a subtype_indication or range that defines a nonstatic or null range, only if it is the single discrete_choice of its discrete_choice_list, and there is only one array_component_association in the array_aggregate.

1 Like

Well, thanks for that, but I don’t see what I’m doing wrong. This seems an extraordinary limitation.

Isn’t it that you have to drop the others? I wonder if a default_component_value would work. If that’s the case then I think I have thought it strange too.

@JC001 is correct. if you have a dynamic choice in an array aggregate, it must be the only component association in the aggregate. On the other hand, array aggregates can now be specified using “for X in ... =>” and this gives you a lot of flexibility. Several folks suggested this, the simplest being the one from @OneWingedShark (slightly adjusted here):

  Window : constant Bit_Window_Content :=
    (for I in Bit_Window_Content'Range => I = ID_In_Window)

I agree the rule given in RM 4.3.3(17) seems kind of arbitrary, but it is one of those rules that has been there forever, and no one has bothered to wonder whether it could be relaxed. In fact, the Ada 83 rule was slightly more limiting, and so the current rule (which dates from Ada 95) was a slight relaxation on that one. Feel free to make a suggestion on the ARG GitHub issues list that this rule be relaxed further …

2 Likes

What you’re doing wrong is violating a language rule. Whether the rule is necessary or could be relaxed is a different question.

For a one-dimensional array, I see two options:

procedure Non_Static_Agg is
   type I16 is range 0 .. 15;
   type List is array (I16) of Boolean;

   procedure Try (Index : in I16);

   procedure Try (Index : in I16) is
      A : constant List := (List'First .. Index - 1 => False) & True & (Index + 1 .. List'Last => False);

      B : List := (others => False);
   begin -- Try
      B (Index) := True;
   end Try;
begin -- Non_Static_Agg
   null;
end Non_Static_Agg;

The rule may be related to the rule for discriminant values in record aggregates. Many people are confused by

type Disc is range 1 .. 4;

type Rec (D : Disc) is record
   case D is
   when 1 .. 2 =>
      A : Integer;
   when 3 .. 4 =>
      B : Float;
   end case;
end record;
...
function New_Rec (D : in Disc) return Rec is
begin
   case D is
   when 1 .. 2 =>
      return (D => D, A => -3);
...

being illegal. The value for the discriminant must be static. Instead, one can use

   when 1 .. 2 =>
      declare
         R : Rec (D => D);
      begin
         R.A := -3;

         return R;
      end;

(an extended return can also be used). This is similar to the use of B in the array example.

I see I managed it in 2016.

If you still have that odious “they declare each register with its own array-type” then use a generic.

-- Assuming: Type Bit is new Boolean with Size => 1;
Generic
   Type Index is (<>);
   Type Bit_Vector is array (Index range <>) of Bit;
Function Value( Input : Index ) return Bit_Vector;

Function Value( Input : Index ) return Bit_Vector is
Begin
   Return Result : Bit_Vector:= (Index'Range => False) do
      Result(Input) := True;
   End return;
End Value;

If you’re having to deal with anonymous types (e.g. A : Array (1..8) of Bit;) the easiest way might be overlays+generics.

If it’s just something as cumbersome as Type AX_Type is... followed by the variables, then I would use generics+overloading to provide a usable/consistent interface:

-- Gven:
Bit_Width : Constant 32;
Type Generic_Register of Array(1..Bit_Width) of Bit;
Type Register_AX is new Generic_Register;
Type Register_BX is new Generic_Register;

AX : Register_AX;
BX : Register_BX;

-- Use something like:

Type Bit_Index is range 1..Bit_Width; -- Mirroring the register's.

Generic
   Type Register_Type is Array(1..Bit_Width) of Bit;
   R : in out Register;
Procedure Set_Bit( X : Bit_Index );
Procedure Set_Bit( X : Bit_Index ) is
   Actual_Index : Constant := R'First + (X-Bit_Index'First);
Begin
   R( Actual_Index ):= True;
End Set_Bit;
--...same for unset;

I ended up declaring a blank (but preset) data object, then filling in the blanks.

      Data : MEIEA_Data;
   begin
      Data.INDEX := UInt5 (ID / 16);
      Data.WINDOW (Integer (ID mod 16)) := True;

There’s a lot to be said for simple.

Thanks to everyone for all the suggestions.

2 Likes