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

1

5

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.

Piezoelectricity answered 16/5, 2018 at 3:56 Comment(6)
The take away from the discussion at comp.lang.ada (groups.google.com/forum/#!topic/comp.lang.ada/dZIHeAnlu9I) on this topic indicates that representation clauses are insufficient for assuring portable code for LE and BE HW in standard Ada. It seems the recommendation is to define the Header_IPv4 type in a package spec. together with a subprogram that converts a byte array into a Header_IPv4 instance. The HW specific implementation is to be placed in the body of the package. It means the build system must support selection of specific body file to include in the build (gprbuild can).Adynamia
Optikos, since you are using a non-GNAT Ada compiler you are probably using another build system than gprbuild though. Hope you can find a way to make this work if you decide to implement this solution. Or better yet, I hope somebody else here on Stackoverflow has an even better suggestion!Adynamia
The perennial claim is that Bit_Order •alone• does not solve the heterogenous-endianness problem. The perennial claim is that record representation clause •alone• (especially with •same•-numbered bitfield offsets between the heterogenous & homogenous cases) does not solve the heterogogenous-endianness problem. But note that I utilized Ada's equivalent of VHDL's downto in all those ”31 minus“es there. I am thinking that 3 features utilized together actually does accomplish the goal of manually encoding the implicit endian byte swap via existing Ada-language features.Piezoelectricity
"Plus I am using a non-GNAT Ada compiler, so using the GNAT-specific feature is unavailable to me." ISO-standard Ada is ISO/IEC 8652:2012. GNAT is the only compiler that implements this. Since you're using another compiler, you must be asking about an earlier version of the standard. It would be helpful if you specified which version you're asking about.Quenchless
I was merely precluding any just-compliantly-use-Scalar_Storage_Order answers (which I won't accept). I actually am reluctantly using GNAT for now, but will be switching to another vendor's Ada compiler as soon as it supports Ada2012. Hence, I don't want to rely on any GNAT-specific language extensions at all, especially if they don't look like AI12-0218 is going to be included in the Ada2020 standard (due to lack of ARG sponsor).Piezoelectricity
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.Piezoelectricity
E
0

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.

Ecesis answered 30/4, 2019 at 14:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.