In only ISO standard Ada, how can Record Representation Clause + any other language feature(s) be portable to little-endian and big-endian processors? - standards

Without utilizing the nonstandard‡ Scalar_Storage_Order clause in recent releases of GNAT, how can, say, the IPv4 header be portably represented via Record Representation Clause(s) in conjunction with any combination of any other language features, so that “the same” code works on both little-endian and big-endian processors but be emitted on the wire (e.g., via, say, the payload of an Ethernet frame) in what IETF calls network byte order (which is IETF's fancy name for big-endian). In C, “the same” code could utilize preprocessor macros to perform byte-swapping on little-endian processors, but be a no-op on big-endian processors, but standard Ada has no preprocessor. In C++, “the same” code could utilize meta-template programming (MTP) to perform byte-swapping on little-endian processors, but be a no-op on big-endian processors, but standard Ada lacks MTP.
(Btw, much the same issue arises in a device driver when a big-endian processor interfaces with a little-endian peripheral IC's memory-mapped register, or vice versa: little-endian processor interfaces with a big-endian IC's memory-mapped register.)
BytesPerWord : constant := 4;
BitsPerByte : constant := 8;
PowerOf2Highest : constant := BytesPerWord*BitsPerByte - 1; -- part #1 of byte-swap
type Header_IPv4 is record
Version : integer range 0 .. F#16;
IHL : integer range 0 .. F#16;
TOS : integer range 0 .. FF#16;
Length : integer range 0 .. FF#16;
Ident : integer range 0 .. FFFF#16;
Flags : integer range 0 .. 7#16;
Frag_Offs : integer range 0 .. 1FFF#16;
end record;
type Header_IPv4_Homogenous is new Header_IPv4;
for Header_IPv4_Homogenous use record -- Good-to-go for big-endian processors
Version at 0*BytesPerWord range 0 .. 3;
IHL at 0*BytesPerWord range 4 .. 7;
TOS at 0*BytesPerWord range 8 .. 15;
Length at 0*BytesPerWord range 16 .. 31;
Ident at 1*BytesPerWord range 0 .. 15;
Flags at 1*BytesPerWord range 16 .. 18;
Frag_Offs at 1*BytesPerWord range 19 .. 31;
end record;
for Header_IPv4_Homogenous'Alignment use 4;
for Header_IPv4_Homogenous'Bit_Order use High_Order_First;
type Header_IPv4_Heterogenous is new Header_IPv4;
for Header_IPv4_Heterogenous use record -- Good-to-go??? for little-endian processors?
Version at 0*BytesPerWord range PowerOf2Highest- 3 .. PowerOf2Highest- 0; -- p
IHL at 0*BytesPerWord range PowerOf2Highest- 7 .. PowerOf2Highest- 4; -- a
TOS at 0*BytesPerWord range PowerOf2Highest- 15 .. PowerOf2Highest- 8; -- r
Length at 0*BytesPerWord range PowerOf2Highest- 31 .. PowerOf2Highest- 16; -- t
Ident at 1*BytesPerWord range PowerOf2Highest- 15 .. PowerOf2Highest- 0; --
Flags at 1*BytesPerWord range PowerOf2Highest- 18 .. PowerOf2Highest- 16; -- #
Frag_Offs at 1*BytesPerWord range PowerOf2Highest- 31 .. PowerOf2Highest- 19; -- 2
end record;
for Header_IPv4_Heterogenous'Alignment use 4;
for Header_IPv4_Heterogenous'Bit_Order use Low_Order_First; -- part #3 of byte-swap
Note how “PowerOf2Highest minus” and ‘reversing’ the big-endian's bit ids from (from,to) order to [visually, not arithmetically really] (to,from) order are utilized in part #2 of the byte-swap as a rough equivalent of VHDL's downto, which is a key portion of how VHDL would solve this heterogenous-endianness problem. (VHDL is a cousin language to Ada83.)
But now, how to obfuscate which member of the set {Header_IPv4_Homogenous, Header_IPv4_Heterogenous} has been chosen as the type name Header_IPv4_Portable in app-domain-code? Use child packages?
‡ Scalar_Storage_Order has been proposed as a potential feature for the next edition of the ISO standard of Ada, but so far there is no official sponsor championing the proposal in the ISO standardization committee, so the proposal for standardization could whither & die on the vine. Plus I shall use a non-GNAT Ada compiler, so using the GNAT-specific feature is unavailable to me.

The portion of the solution above was presaged by Norman Cohen in
ada-auth.org/ai-files/grab_bag/bitorder.pdf almost 20 years ago, but
what is missing both here and in his document is the way of swapping
in the correct Record Representation Clause via, say, different child
packages in various Ada compilers. How to do that child-package
conditional linkage in all the Ada compilers is what I am looking for
now.
The traditional way to do so would be via multiple files and the project-manager supplying the proper one as the compilation is done.
Perhaps there is an alternate method we can use though; I think the following should work, I've compiled it but haven't tested it:
Package IPv4 is
Type Header_IPv4 is private;
Function Version ( Object : Header_IPv4 ) return Integer;
Function IHL ( Object : Header_IPv4 ) return Integer;
Function TOS ( Object : Header_IPv4 ) return Integer;
Function Length ( Object : Header_IPv4 ) return Integer;
Function Ident ( Object : Header_IPv4 ) return Integer;
Function Flags ( Object : Header_IPv4 ) return Integer;
Function Frag_Offs ( Object : Header_IPv4 ) return Integer;
-- If you need to write fields, use:
-- Procedure Field ( Object : in out Header_IPv4; Value : Integer );
Private
Header_Size : Constant := 7 * (4*8); -- 7 Integers of 4-bytes.
type Base_IPv4 is record
Version : integer range 0 .. 16#F#;
IHL : integer range 0 .. 16#F#;
TOS : integer range 0 .. 16#FF#;
Length : integer range 0 .. 16#FF#;
Ident : integer range 0 .. 16#FFFF#;
Flags : integer range 0 .. 16#7#;
Frag_Offs : integer range 0 .. 16#1FFF#;
end record
with Size => Header_Size, Object_Size => Header_Size;
type Header_IPv4 is null record
with Size => Header_Size, Object_Size => Header_Size;
End IPv4;
Package Body IPv4 is
Package Internal is
Use System;
BytesPerWord : constant := 4;
BitsPerByte : constant := 8;
PowerOf2Highest : constant := BytesPerWord*BitsPerByte - 1; -- part #1 of byte-swap
type Header_IPv4_Homogenous is new Base_IPv4;
for Header_IPv4_Homogenous use record -- Good-to-go for big-endian processors
Version at 0*BytesPerWord range 0 .. 3;
IHL at 0*BytesPerWord range 4 .. 7;
TOS at 0*BytesPerWord range 8 .. 15;
Length at 0*BytesPerWord range 16 .. 31;
Ident at 1*BytesPerWord range 0 .. 15;
Flags at 1*BytesPerWord range 16 .. 18;
Frag_Offs at 1*BytesPerWord range 19 .. 31;
end record;
for Header_IPv4_Homogenous'Alignment use 4;
for Header_IPv4_Homogenous'Bit_Order use High_Order_First;
type Header_IPv4_Heterogenous is new Base_IPv4;
for Header_IPv4_Heterogenous use record -- Good-to-go??? for little-endian processors?
Version at 0*BytesPerWord range PowerOf2Highest- 3 .. PowerOf2Highest- 0; -- p
IHL at 0*BytesPerWord range PowerOf2Highest- 7 .. PowerOf2Highest- 4; -- a
TOS at 0*BytesPerWord range PowerOf2Highest- 15 .. PowerOf2Highest- 8; -- r
Length at 0*BytesPerWord range PowerOf2Highest- 31 .. PowerOf2Highest- 16; -- t
Ident at 1*BytesPerWord range PowerOf2Highest- 15 .. PowerOf2Highest- 0; --
Flags at 1*BytesPerWord range PowerOf2Highest- 18 .. PowerOf2Highest- 16; -- #
Frag_Offs at 1*BytesPerWord range PowerOf2Highest- 31 .. PowerOf2Highest- 19; -- 2
end record;
for Header_IPv4_Heterogenous'Alignment use 4;
for Header_IPv4_Heterogenous'Bit_Order use Low_Order_First; -- part #3 of byte-swap
Function Convert_Heterogenous is new Ada.Unchecked_Conversion(
Source => Header_IPv4,
Target => Header_IPv4_Heterogenous
);
Function Convert_Homogenous is new Ada.Unchecked_Conversion(
Source => Header_IPv4,
Target => Header_IPv4_Homogenous
);
Function Convert_Heterogenous is new Ada.Unchecked_Conversion(
Source => Header_IPv4_Heterogenous,
Target => Header_IPv4
);
Function Convert_Homogenous is new Ada.Unchecked_Conversion(
Source => Header_IPv4_Homogenous,
Target => Header_IPv4
);
End Internal;
Function Convert( Object : Header_IPv4 ) return Base_IPv4 is
use Internal, System;
Begin
if Default_Bit_Order = High_Order_First then
Return Base_IPv4( Convert_Homogenous(Object) );
else
Return Base_IPv4( Convert_Heterogenous(Object) );
end if;
End Convert;
Function Version ( Object : Header_IPv4 ) return Integer is
(Convert(Object).Version);
Function IHL ( Object : Header_IPv4 ) return Integer is
(Convert(Object).IHL);
Function TOS ( Object : Header_IPv4 ) return Integer is
(Convert(Object).TOS);
Function Length ( Object : Header_IPv4 ) return Integer is
(Convert(Object).Length);
Function Ident ( Object : Header_IPv4 ) return Integer is
(Convert(Object).Ident);
Function Flags ( Object : Header_IPv4 ) return Integer is
(Convert(Object).Flags);
Function Frag_Offs ( Object : Header_IPv4 ) return Integer is
(Convert(Object).Frag_Offs);
End IPv4;
Another alternative could be had by using the read/write attributes, although this form would not allow memory-mapping the IPv4-typed variable and correctly reading it, it should suffice for stream-based processing, and much simpler than what's here.

Related

Is it necessary to wrap shared array data in a protected type?

I am aware that it is generally bad practice (and the ARM probably says that this is undefined behavior), but I am attempting to write a fast text parser containing many floating point numbers and it would be very expensive to wrap the loaded text into a protected type given that the data is examined character by character and may have up to a million floats or pass a slice on the stack.
Is it possible in Ada (GNAT) to "safely" divide up an unprotected array for consumption with multiple tasks given that the array is never written and only read?
As in:
Text : array (1..1_000_000) of Character := ...
begin
Task_1.Initialize (Start_Index => 1, End_Index => 10_000);
Task_2.Initialize (Start_Index => 10_001, End_Index => 20_000);
...
Yes. That is safe because there is no race condition associated with reading the data and there is no temporally overlapping write operation.
For example, the following code uses such a technique to perform parallel addition on an array of integers.
package Parallel_Addition is
type Data_Array is array(Integer range <>) of Integer;
type Data_Access is access all Data_Array;
function Sum(Item : in not null Data_Access) return Integer;
end Parallel_Addition;
package body Parallel_Addition is
---------
-- Sum --
---------
function Sum (Item : in not null Data_Access) return Integer is
task type Adder is
entry Set (Min : Integer; Max : Integer);
entry Report (Value : out Integer);
end Adder;
task body Adder is
Total : Integer := 0;
First : Integer;
Last : Integer;
begin
accept Set (Min : Integer; Max : Integer) do
First := Min;
Last := Max;
end Set;
for I in First .. Last loop
Total := Total + Item (I);
end loop;
accept Report (Value : out Integer) do
Value := Total;
end Report;
end Adder;
A1 : Adder;
A2 : Adder;
R1 : Integer;
R2 : Integer;
Mid : constant Integer := (Item'Length / 2) + Item'First;
begin
A1.Set (Min => Item'First, Max => Mid);
A2.Set (Min => Mid + 1, Max => Item'Last);
A1.Report (R1);
A2.Report (R2);
return R1 + R2;
end Sum;
end Parallel_Addition;
with Parallel_Addition; use Parallel_Addition;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Calendar; use Ada.Calendar;
procedure Parallel_Addition_Test is
The_Data : Data_Access := new Data_Array (1 .. Integer'Last / 5);
Start : Time;
Stop : Time;
The_Sum : Integer;
begin
The_Data.all := (others => 1);
Start := Clock;
The_Sum := Sum (The_Data);
Stop := Clock;
Put_Line ("The sum is: " & Integer'Image (The_Sum));
Put_Line
("Addition elapsed time is " &
Duration'Image (Stop - Start) &
" seconds.");
Put_Line
("Time per addition operation is " &
Float'Image(Float(Stop - Start) / Float(The_Data'Length)) &
" seconds.");
end Parallel_Addition_Test;

Different scalar range in different cirumstance

How to represent data with complete scalar range in the first state then represent it as zero to one in the next state while using the same memory space?
Any approaches to the problem is appreciated, the example procedures does not have to be like that if solution requires them to change.
Example
Reading values from a file and then normalize it. Float_Array is for raw value with any range that comes directly from file.
Feature_Array is for normalized values.
type Float_Array is array (Integer range <>) of Float;
type Feature is new Float range 0.0 .. 1.0;
type Feature_Array is array (Integer range <>) of Feature;
The first step is to read floats into an Float_Array and finding max and min value.
procedure Read (Name : String; Result : out Float_Array; Last : out Integer; Min : out Float; Max : out Float) is
use Ada.Text_IO;
use Ada.Float_Text_IO;
F : File_Type;
begin
Open (F, In_File, Name);
for I in Result'Range loop
exit when End_Of_File (F);
Get (F, Result (I));
Min := Float'Min (Min, Result (I));
Max := Float'Max (Max, Result (I));
Last := I;
end loop;
Close (F);
end;
Float_Array is just temporarily being used to read and find min max. The next step is to normalize all values.
function Normalize (Value : Float; Min, Max : Float) return Float is
begin
return (Value - Min) / (Max - Min);
end;
procedure Normalize (Min : Float; Max : Float; Scale : Float; Result : in out Float_Array) is
begin
for E of Result loop
E := Normalize (E, Min, Max) * Scale;
end loop;
end;
After normalization I want the values to be represented as Feature_Array.
Bad solution that does no range check.
There is no range check so it is not a proper solution. Scaling the values from one to three does not yield range check error. So at this point there is no point to have Feature_Array if there is no range check.
Last : Integer;
Data : Float_Array (1 .. 100);
Min : Float := Float'First;
Max : Float := Float'Last;
begin
Read ("frequency.lines_of_float", Data, Last, Min, Max);
Normalize (Min, Max, 1.0, Data);
-- Normalize (Min, Max, 3.0, Data);
declare
The_Features : Feature_Array (Data'Range) with Address => Data'Address;
begin
Put (The_Features);
end;
I have tried attribute 'Valid on the array i.e. The_Features'Valid but it only works on scalar types. And using 'Valid for range check will involve extra code.
I think that I finally understand what is needed here. You want to have variable of normalized type and not of Floats. (in case of floats one would have to constantly do array overlays or have 2 variables pointing to the same address).
Last : Integer;
The_Features : Feature_Array (1 .. 100);
Min : Float := Float'First;
Max : Float := Float'Last;
begin
declare
Data : Float_Array (The_Features'Range) with Address => The_Features'Address;
begin
Read ("frequency.lines_of_float", Data, Last, Min, Max);
Normalize (Min, Max, 1.0, Data);
-- Normalize (Min, Max, 3.0, Data);
end;
Put (The_Features);
This should work but keep in mind that you have to ensure that the result of Normalize is valid.
It seems that manual range checking is the way to go. I can't find a way to use Ada range checking automatically.
To manually check an array of float is within a range
This uses Ada 2012 - conditional expressions.
This is needed sometimes when variables depends on address.
A := (for all E of Item (Item'First .. Last) => E'Valid);
Assert (A, "Elements of Item is not within range.");
Code
with Ada.Text_IO;
with Ada.Float_Text_IO;
procedure Main is
type Float_Array is array (Integer range <>) of Float;
type Feature is new Float range 0.0 .. 1.0;
type Feature_Array is array (Integer range <>) of Feature;
procedure Read (Name : String; Result : out Float_Array; Last : in out Integer; Min : in out Float; Max : in out Float) is
use Ada.Text_IO;
use Ada.Float_Text_IO;
F : File_Type;
begin
Open (F, In_File, Name);
loop
exit when End_Of_File (F);
Last := Last + 1;
Get (F, Result (Last));
Skip_Line (F);
Min := Float'Min (Min, Result (Last));
Max := Float'Max (Max, Result (Last));
exit when Last = Result'Last;
end loop;
Close (F);
end;
function Normalize (Value : Float; Min, Max : Float) return Float is ((Value - Min) / (Max - Min));
procedure Normalize (Min : Float; Max : Float; Scale : Float; Result : in out Float_Array) is
begin
for E of Result loop
E := Normalize (E, Min, Max) * Scale;
end loop;
end;
procedure Put (Item : Feature_Array) is
use Ada.Float_Text_IO;
use Ada.Text_IO;
begin
for E of Item loop
Put (Float (E), 3, 3, 0);
New_Line;
end loop;
end;
procedure Put (Item : Float_Array) is
use Ada.Float_Text_IO;
use Ada.Text_IO;
begin
for E of Item loop
Put (E, 3, 3, 0);
New_Line;
end loop;
end;
procedure Read (Item : out Feature_Array; Last : in out Integer) with
Pre => Feature_Array'Component_Size = Float_Array'Component_Size,
Post => (for all E of Item (Item'First .. Last) => E >= 0.0 and E <= 1.0);
procedure Read (Item : out Feature_Array; Last : in out Integer) is
Data : Float_Array (Item'Range) with Address => Item'Address;
Min : Float := Float'Last;
Max : Float := Float'First;
begin
Read ("f.ssv", Data, Last, Min, Max);
Ada.Text_IO.Put_Line ("Before normalization.");
Put (Data (Data'First .. Last));
Normalize (Min, Max, 1.0, Data (Data'First .. Last));
end;
F : Feature_Array (-5 .. 10);
Last : Integer := F'First - 1;
begin
Read (F, Last);
Ada.Text_IO.Put_Line ("After normalization.");
Put (F (F'First .. Last));
end;
f.ssv
0.1
11.0
-3.0
Result
Before normalization.
0.100
11.000
-3.000
After normalization.
0.221
1.000
0.000

Ada: Flatten Record to Byte array

I have a record made of smaller types, a total of 16 bits in size.
type Type_1 is (val1, val2, val3, val4, val5, val6, val7, val8);
for Type_1 use (val1 => 0, val2 = 1, val3 = 2, val4 => 3
val5 => 4, val6 => 5, val7 => 6, val8 => 7);
for Type_1'Size use 3;
type Type_2 is (val1, val2);
for Type_2 use (val1 => 0, val2 = 1);
for Type_2'Size use 1;
etc, etc
type The_Record is record
element_1 : Type_1;
element_2 : Type_2;
element_3 : Type_3;
element_4 : Type_4;
end record;
for the_Record use record
element_1 at 0 range 0 .. 2;
element_2 at 0 range 3 .. 4;
element_3 at 0 range 5 .. 12;
element_4 at 0 range 13 .. 15;
end record;
for The_Record'Size use 16;
How can I flatten 'The_Record' into an array of bytes or something similar?
Thank you!
You can always use Unchecked_conversion, however, this approach is unchecked and you are therefore telling the compiler (a) that you know what you're doing, and (b) that it cannot help you [otherwise it would be named checked_conversion].
Another way that you can do the conversion is with overlaying, this is my preferred approach (if it is not a simple type-renaming issue) as if things change then the conversion-function can be modified as needed.
-- Subtype-renaming.
Subtype Byte is Interfaces.Unsigned_8;
-- General type for conversion to a collection of bytes.
Type Byte_Array is Array (Positive Range <>) of Byte;
-- Speciffic collection of bytes from The_Record.
Subtype Record_Bytes is Byte_Array(1..The_Record'Size/Byte'Size);
-- Converting record to bytes...
Function Convert( Input : The_Record ) return Record_Bytes is
Result : Constant Record_Bytes;
For Result'Address use Input'Address;
Pragma Import( Convention => Ada, Entity => Result );
begin
Return Result;
end Convert;
-- Converting bytes to record... in Ada 2012!
Function Convert( Input : Record_Bytes ) return The_Record is
Result : The_Record with
Import, Convention => Ada, Address => Input'Address;
begin
Return Result;
end Convert;
Another, and probably better, way (if you are trying for serialization. as I suspect) of doing this would be to create read/write functions and override the default 'read and 'write attributes.
This in an example of how to use the Unchecked Conversion way:
with Ada.Unchecked_Conversion;
with Ada.Text_Io;
procedure Uc is
type The_Record is record
A : Integer;
B : Integer;
end record;
-- define a byte sized thing...
type U8 is mod 2**8;
for U8'Size use 8;
-- have your array defined according to
-- the size of the record it needs to hold
type U8_Record_Array is array (1 .. The_Record'Size / U8'Size) of U8;
-- instantiate Unchecked_Conversion
function To_Array is new Ada.Unchecked_Conversion (Source => The_Record,
Target => U8_Record_Array);
-- Declare a record and convert it to an array
R : constant The_Record := (A => 1, B => 2);
A : constant U8_Record_Array := To_Array (R);
begin
-- Print the Array As Bytes
for I in A'Range loop
Ada.Text_Io.Put (U8'Image (A(I)));
end loop;
end Uc;
Depending on Exactly what you are intending to do, Both the Overlay & Unchecked Conversion ways will have pros & cons associated with them :)

Is overflow defined for VHDL numeric_std signed/unsigned

If I have an unsigned(MAX downto 0) containing the value 2**MAX - 1, do the VHDL (87|93|200X) standards define what happens when I increment it by one? (Or, similarly, when I decrement it by one from zero?)
Short answer:
There is no overflow handling, the overflow carry is simply lost. Thus the result is simply the integer result of your operation modulo 2^MAX.
Longer answer:
The numeric_std package is a standard package but it is not is the Core the VHDL standards (87,93,200X).
For reference : numeric_std.vhd
The + operator in the end calls the ADD_UNSIGNED (L, R : unsigned; C : std_logic) function (with C = '0'). Note that any integer/natural operand is first converted into an unsigned.
The function's definition is:
function ADD_UNSIGNED (L, R : unsigned; C : std_logic) return unsigned is
constant L_left : integer := L'length-1;
alias XL : unsigned(L_left downto 0) is L;
alias XR : unsigned(L_left downto 0) is R;
variable RESULT : unsigned(L_left downto 0);
variable CBIT : std_logic := C;
begin
for i in 0 to L_left loop
RESULT(i) := CBIT xor XL(i) xor XR(i);
CBIT := (CBIT and XL(i)) or (CBIT and XR(i)) or (XL(i) and XR(i));
end loop;
return RESULT;
end ADD_UNSIGNED;
As you can see an "overflow" occurs if CBIT='1' (carry bit) for i = L_left. The result bit RESULT(i) is calculated normally and the last carry bot value is ignored.
I've had the problem with wanting an unsigned to overflow/underflow as in C or in Verilog and here is what I came up with (result and delta are unsigned):
result <= unsigned(std_logic_vector(resize(('1' & result) - delta, result'length))); -- proper underflow
result <= unsigned(std_logic_vector(resize(('0' & result) + delta, result'length))); -- proper overflow
For overflow '0' & result makes an unsigned which is 1 bit larger to be able to correctly accommodate the value of the addition. The MSB is then removed by the resize command which yields the correct overflow value. Same for underflow.
For a value of MAX equal to 7 adding 1 to 2**7 - 1 (127) will result in the value 2**7 (128).
The maximum unsigned value is determined by the length of an unsigned array type:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity foo is
end entity;
architecture faa of foo is
constant MAX: natural := 7;
signal somename: unsigned (MAX downto 0) := (others => '1');
begin
UNLABELED:
process
begin
report "somename'length = " & integer'image(somename'length);
report "somename maximum value = " &integer'image(to_integer(somename));
wait;
end process;
end architecture;
The aggregate (others => '1') represents a '1' in each element of somename which is an unsigned array type and represents the maximum binary value possible.
This gives:
foo.vhdl:15:9:#0ms:(report note): somename'length = 8
foo.vhdl:16:9:#0ms:(report note): somename maximum value = 255
The length is 8 and the numerical value range representable by the unsigned array type is from 0 to 2**8 - 1 (255), the maximum possible value is greater than 2**7 (128) and there is no overflow.
This was noticed in a newer question VHDL modulo 2^32 addition. In the context of your accepted answer it assumes you meant length instead of the leftmost value.
The decrement from zero case does result in a value of 2**8 - 1 (255) (MAX = 7). An underflow or an overflow depending on your math religion.
Hat tip to Jonathan Drolet for pointing this out in the linked newer question.

Convert integer to record

I get an 16 Bit integer from C. This integer consists of 16 flags.
How can I convert this integer in a record of 16 booleans?
Thanks!
type Flags is record
Flag1 : Boolean;
Flag2 : Boolean;
- ...
Flag15 : Boolean;
end record;
for Flags use record
Flag1 at 0 range 0 .. 0;
Flag2 at 0 range 1 .. 1;
-- ...
Flag15 at 0 range 15 .. 15;
end record;
for Flags'Size use 16;
-- This is vital, because normally, records are passed by-reference in Ada.
-- However, as we use this type with C, it has to be passed by value.
-- C_Pass_By_Copy was introduced in GNAT and is part of the language since Ada 2005.
pragma Convention (C_Pass_By_Copy, Flags);
You can use this type directly in the declaration of the imported C function instad of the C integer type.
You can just simply perform 16 right bit shifts and bitwise AND the result with 1 to determine whether or not a bit/flag is set. Here's an example (I'm hoping this isn't homework):
#include <stdio.h>
#include <stdint.h>
typedef unsigned char BOOL;
int main(void)
{
unsigned i;
uint16_t flags = 0x6E8B; /* 0b0110111010001011 */
BOOL arr[16];
for (i = 0; i < 16; i++) {
arr[i] = (flags >> i) & 1;
printf("flag %u: %u\n", i+1, arr[i]);
}
return 0;
}
arr[0] will contain the least significant bit, and arr[15] the most significant.
Output:
flag 1: 1
flag 2: 1
flag 3: 0
flag 4: 1
flag 5: 0
flag 6: 0
flag 7: 0
flag 8: 1
flag 9: 0
flag 10: 1
flag 11: 1
flag 12: 1
flag 13: 0
flag 14: 1
flag 15: 1
flag 16: 0
In Ada, you can usually declare an imported function to take parameters or return values of the type you want, rather than a C-equivalent type which you then have to convert.
So, here, you want
type Flags is array (0 .. 15) of Boolean;
for Flags'Component_Size use 1;
for Flags'Size use 16;
pragma Convention (C, Flags);
and you can declare your function as
function Get_Flags return Flags;
pragma Import (C, Get_Flags, “get_flags");
which with
unsigned short get_flags(void) {
return 0x6e8b;
}
and a simple harness gave me
flag 0 is TRUE
flag 1 is TRUE
flag 2 is FALSE
flag 3 is TRUE
flag 4 is FALSE
flag 5 is FALSE
flag 6 is FALSE
flag 7 is TRUE
flag 8 is FALSE
flag 9 is TRUE
flag 10 is TRUE
flag 11 is TRUE
flag 12 is FALSE
flag 13 is TRUE
flag 14 is TRUE
flag 15 is FALSE
As Bo Persson noted, this is fine so long as your code only needs to run on a little-endian machine. If you want it to run on a SPARC or a Powerbook, it’s probably best to use trashgod’s suggestion;
subtype Flags is Interfaces.C.unsigned_short;
use type Flags;
function Get_Flags return Flags;
pragma Import (C, Get_Flags, "get_flags");
and then, probably, name your flag bits (with something more meaningful!)
Flag_3 : constant Flags := 2#0000_0000_0000_1000#;
or (probably more like the C)
Flag_4 : constant Flags := 2 ** 4;
and then check
(Get_Flags and Flag_3) /= 0
In Ada, a modular type allows logical operations to access a value as a bit set. Introduced in Ada 95, an overview may be found in the Ada 95 Rationale, §3.3.2 Modular Types. Depending on implementation, the pre-defined type Interfaces.C.unsigned_short my be a convenient choice for obtaining the value from C.
You can also use overlaying to achieve the desired result; let's assume that these booleans are all meaningful and strictly boolean (i.e. nothing that's an enumeration).
First you need to define your record; I'm going to be using a single Nybble to illustrate, but the principle is applicable. The Nybble is the old DOS attributes: reading (visibility-wise; should be Is_Hidden, in retrospect), write, archive, and system.
Type Nyble_Data is mod 2**4;
For Nyble_Data'Size use 4;
Type Data_Record is record
Can_Read, Can_Write, Is_Archived, Is_System : Boolean:= False;
end record;
-- Ensure 4 bits used.
pragma Pack (Data_Record);
For Data_Record'Size use 4;
-- Specify Layout.
For Data_Record use
record
Can_Read at 0 range 0..0;
Can_Write at 0 range 1..1;
Is_Archived at 0 range 2..2;
Is_System at 0 range 3..3;
end record;
-- This is where the magic occurs.
Function Convert( Data : In Nyble_Data ) Return Data_Record is
Result : Data_Record;
For Result'Address use Data'Address;
Pragma Import( Convention => Ada, Entity => Result );
Pragma Inline( Convert );
begin
Return Result;
end Convert;
-- Test variables.
Input : Nyble_Data:= 5;
Output : Data_Record:= Convert(Input);
-- Display the record.
Procedure Put( Data : In Data_Record ) is
Use Ada.Text_IO;
begin
Put_Line( "Read: " & ASCII.HT & Boolean'Image(Data.Can_Read) );
Put_Line( "Write: " & ASCII.HT & Boolean'Image(Data.Can_Write) );
Put_Line( "Archive:" & ASCII.HT & Boolean'Image(Data.Is_Archived) );
Put_Line( "System: " & ASCII.HT & Boolean'Image(Data.Is_System) );
end Put;
You can use a union containing a short int (or a int_16) and a bit field:
union UMyFlags {
short n;
struct {
flag_1 : 1;
flag_2 : 1;
// other flags ...
} flags;
};
However, because of byte ordering, your code will not be portable on every platform.

Resources