From 78b1ff6639957504191e8e72005cadfb5a59c496 Mon Sep 17 00:00:00 2001 From: rogers Date: Fri, 7 May 2021 18:12:50 -0500 Subject: [PATCH] New content for Querying Representation First full version for Specifying Representation --- .../chapters/images/scalar_types_tree.png | Bin 0 -> 25289 bytes .../chapters/interacting_with_devices.rst | 117 +- .../chapters/low_level_programming.rst | 1299 ++++++++++++++--- 3 files changed, 1237 insertions(+), 179 deletions(-) create mode 100644 content/courses/intro-to-embedded-sys-prog/chapters/images/scalar_types_tree.png diff --git a/content/courses/intro-to-embedded-sys-prog/chapters/images/scalar_types_tree.png b/content/courses/intro-to-embedded-sys-prog/chapters/images/scalar_types_tree.png new file mode 100644 index 0000000000000000000000000000000000000000..dce83b9aba79828aeb7eceafc00901654ba42e08 GIT binary patch literal 25289 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 literal 0 HcmV?d00001 diff --git a/content/courses/intro-to-embedded-sys-prog/chapters/interacting_with_devices.rst b/content/courses/intro-to-embedded-sys-prog/chapters/interacting_with_devices.rst index bbf80855..57587bad 100644 --- a/content/courses/intro-to-embedded-sys-prog/chapters/interacting_with_devices.rst +++ b/content/courses/intro-to-embedded-sys-prog/chapters/interacting_with_devices.rst @@ -3,17 +3,120 @@ Interacting with Devices .. include:: ../../global.txt -Non-Memory-Mapped Abstractions ------------------------------- - -.. todo:: - - Complete section! - Memory-Mapped Abstractions -------------------------- +Earlier we said that we could query the address of some object, and we +also showed how to use that result to specify the address of some other +object. We used that capability to create an overlay, in which two +objects are used to refer to the memory shared by those objects. As we +indicated in that discussion, you would not use the same type for each +object |mdash| the point, after all, is to provide a view of the shared +underlying memory cells that is not already available otherwise. Each +distinct type, in other words, would provide a set of operations +providing some required functionality. + +For example, here's an overlay of a (presumably) 32-bit integer type and +a 32-bit array type: + +.. code-block:: ada + + type Bits32 is array (0 .. 31) of Boolean + with Component_Size => 1; + + X : Integer; + Y : Bits32 with Address => X'Address; + +Because one view is as an integer and the +other as an array, we can access that memory using different operations. +Via the array object Y we can access individual bits of the memory +shared with X. Via the integer X, we can do arithmetic on the contents +of that memory. + +Very often, though, there is only on Ada object that we place at +some specific address. That's because the object is meant to be the +software interface to some memory-mapped hardware device. In this +scenario we don't have two overlaid Ada objects, we just have one. The +other "object" is the hardware device. Since they are at the same memory +locations, accessing the Ada object accesses the device. + +For a real-world but nonetheless simple example, suppose we have a +rotary switch on the front of our embedded computer. Rotating the switch +selects among a limited number of values. This switch allows humans to +provide some very simple input to the software running on the computer. +In the application this example is taken from, the rotary switch +identified the specific computer among several in a rack of computers +and other boards, each computer having been given a different rotary +switch value. + +.. code-block:: ada + + Rotary_Switch : Unsigned_8 with + Address => System.Storage_Elements.To_Address (16#FFC0_0801#); + +We declare the object and also specify the address, but not by querying +some entity. We already know the address from the hardware +documentation. But we cannot simply use an integer address literal from +that documentation because type Address is almost always a private type. +We need a way to compose an Address value from an integer value. The +package :ada:`System.Storage_Elements` defines an integer representation +for Address values, among other useful things, and a way to convert +those integer values to Address values. The function :ada:`To_Address` +does that conversion. + +As a result, in the Ada code, reading the value of the variable +Rotary_Switch reads the number on the actual hardware switch. + +Note that if you specify the wrong address, it is hard to say what +happens. Likewise, it is an error for an Address clause to disobey the +object's Alignment. The error cannot be detected at compile time, in +general, because the Address is not necessarily known at compile time. +There's no requirement for a run-time check for the sake of efficiency, +since efficiency seems paramount here. Consequently, this misuse of +Address clauses is just like any other misuse of Address clauses |mdash| +execution of the code is erroneous, meaning all bets are off. You need +to know what you're doing. + +What about writing to the variable? Is that meaningful? In this case, +no. But for some other device it could be meaningful, certainly. It just +depends on the hardware. But in this case, writing to the Rotary_Switch +variable would have no effect, which could be confusing to programmers. +It looks like a variable, after all. We wouldn't declare it as a constant +because the human user could rotate the switch, resulting in a different +value read. Therefore, we hid the Ada variable behind a function, +obviating the entire question. Clients of the function can then use it +for whatever purpose they require, e.g., as the unique identifier for a +computer in a rack. + +Let's talk about the type we use to represent the memory-mapped device. +That type defines the view |mdash| hence the operations |mdash| we have +for the device. + +We choose the type for the representative Ada variable based on the +interface of the hardware mapped to the memory. If the interface is a +single monolithic register, for example, then an integer, either signed +or unsigned, and of the necessary size, will suffice. But suppose the +interface is several bytes wide, and some of the bytes have different +purposes from the others? In that case, a record type is the obvious +solution, with distinct record components dedicated to the different +bytes of the hardware interface. We could use individual bits too, of +course, if that's what the hardware does. Ada is particularly good at +this fine-degree of representation because record components of any +types can be specified in the layout, down to the bit level, within the +record. + + +volatile +atomic +atomic_components +volatile_full_access + +benefit of bit-mapped record fields versus bit-patterns with unsigned types + + + + .. todo:: Complete section! diff --git a/content/courses/intro-to-embedded-sys-prog/chapters/low_level_programming.rst b/content/courses/intro-to-embedded-sys-prog/chapters/low_level_programming.rst index 43664fe1..b6522fbc 100644 --- a/content/courses/intro-to-embedded-sys-prog/chapters/low_level_programming.rst +++ b/content/courses/intro-to-embedded-sys-prog/chapters/low_level_programming.rst @@ -3,6 +3,19 @@ Low Level Programming .. include:: ../../global.txt +This section introduces a number of topics in low-level programming, in +which the hardware and the compiler's representation choices are much +more in view at the source code level. In comparatively high level code +these topics are "abstracted away" in that the programmer can assume +that the compiler does whatever is necessary on the current target +machine so that their code executes as intended. That approach is not +sufficient in low-level programming. + +Note that we do not cover every possibility or language feature. +Instead, we cover the necessary concepts, and also potential surprises +or pitfalls, so that the parts not covered can be learned on your own. + + Separation Principle -------------------- @@ -97,8 +110,8 @@ vendors, if not literally all, implement the Annex so you can rely on the recommended levels of support. -Querying Implementation Limits ------------------------------- +Querying Implementation Limits and Characteristics +-------------------------------------------------- Sometimes you need to know more about the underlying machine than is typical for general purpose applications. For example, your numerical @@ -245,60 +258,53 @@ though. Note that :ada:`Address` is the type of the result of the query attribute :ada:`Address`. -We mentioned potentially needing to swap bytes in networking -communications software, due to the differences in the "endianness" of -the machines communicating. That characteristic is specified in :ada:`System` -as follows: +We mentioned potentially needing to swap bytes in networking +communications software, due to the differences in the "endianness" of +the machines communicating. That characteristic can be determined via a +constant declared in package :ada:`System` as follows: .. code-block:: ada type Bit_Order is (High_Order_First, Low_Order_First); Default_Bit_Order : constant Bit_Order := implementation-defined; -:ada:`High_Order_First` corresponds to "big-endian" and -:ada:`Low_Order_First` to little-endian. Here is a real-world example of -use, in which we retrieve arbitrarily-typed values from a given buffer -starting at a given index: +:ada:`High_Order_First` corresponds to "Big Endian" and +:ada:`Low_Order_First` to "Little Endian." On a Big Endian machine, bit +0 is the most significant bit. On a Little Endian machine, bit 0 is the +least significant bit. + +Strictly speaking, this constant gives us the default order for bits +within storage elements in record representation clauses, not the order +of bytes within words. However, we can usually use it for the byte order +too. In particular, if Word_Size is greater than Storage_Unit, a word +necessarily consists of multiple storage elements, so the default bit +ordering is the same as the ordering of storage elements in a word. + +Let's take that example of swapping the bytes in a received Ethernet +packet. The "wire" format is Big Endian so if we are running on a Little +Endian machine we must swap the bytes received. + +Suppose we want retrieve typed values from a +given buffer or bytes. We get the bytes from the buffer into a variable named Value, of the type of interest, and then swap those bytes within Value if necessary. .. code-block:: ada - generic - type Retrieved is private; - procedure Retrieve_4_Bytes - (Value : out Retrieved; - Buffer : Byte_Array; - Start : Index) - with - Pre => Start in Buffer'Range and then - Start + 3 in Buffer'Range; - - procedure Retrieve_4_Bytes - (Value : out Retrieved; - Buffer : Byte_Array; - Start : Index) - is ... begin - Value := ...; + Value := ... + if Default_Bit_Order /= High_Order_First then -- we're not on a Big Endian machine Value := Byte_Swapped (Value); end if; end Retrieve_4_Bytes; -:ada:`Value` is the output parameter of type :ada:`Retrieved`, which is a generic -formal type. We can instantiate this generic for lots of different kinds -of types, as long as their values occupy four bytes. (We have removed the -code that checks the size, for the sake of simplicity.) Don't worry -about some of the details for now, we'll get there eventually. The point -here is the if-statement: the expression compares the :ada:`Default_Bit_Order` -constant to :ada:`High_Order_First` to see if this execution is on a -big-endian machine. If not, it swaps the bytes because the incoming -bytes are always received in "wire-order," i.e., big-endian order. - -(In reality we would change the left-hand side of the call to "/=" in -the if-statement expression to check more than :ada:`Default_Bit_Order`, -because the code could have used a pragma to change the default scalar -storage order. That's more detail than worth going into here.) +We have elided the code that gets the bytes into Value, for the sake of +simplicity. How the bytes are actually swapped by function Byte_Swapped +is also irrelevant. The point here is the if-statement: the expression +compares the :ada:`Default_Bit_Order` constant to +:ada:`High_Order_First` to see if this execution is on a Big Endian +machine. If not, it swaps the bytes because the incoming bytes are +always received in "wire-order," i.e., Big Endian order. Another important set of declarations in package :ada:`System` define the values for priorities, including interrupt priorities. We will ignore @@ -317,13 +323,14 @@ configurations supported by the implementation. :ada:`System_Name` represents the current machine configuration. We've never seen any actual use of this. -:ada:`Memory_Size` is an implementation-defined value that is intended to -reflect the memory size of the configuration, in units of storage +:ada:`Memory_Size` is an implementation-defined value that is intended +to reflect the memory size of the configuration, in units of storage elements. What the value actually refers to is not specified. Is it the -size of the address space, the amount of physical memory on the machine, -or what? In any case, the amount of memory available to a given computer -is neither dependent upon, nor reflected by, this constant. Consequently, -:ada:`Memory_Size` is not useful either. +size of the address space, i.e., the amount possible, or is it the +amount of physical memory actually on the machine, or what? In any case, +the amount of memory available to a given computer is neither dependent +upon, nor reflected by, this constant. Consequently, :ada:`Memory_Size` +is not useful either. Why have something defined in the language that nobody uses? In short, it seemed like a good idea at the time when Ada was first defined. @@ -334,86 +341,151 @@ the language evolves, just in case somebody does use them. Querying Representation Choices ------------------------------- -Earlier we mentioned the ability to specify a record type's layout, or a -variable's address in memory. These are just some of the possibilities -supported by the language. For any representation characteristic you can -specify, you can also query that characteristic. That can be very handy, -especially if you did not specify it and want to know what the compiler -chose for the representation. +As we mentioned in the introduction, in low-level programming the +hardware and the compiler's representation choices can come to the +forefront. You can, therefore, query many such choices. -For example, say you want to express an "overlay," in which an object of -one type is placed at the same memory location as a distinct object of a -distinct type, thus overlaying one object over the other. Doing so -allows you to interact with that memory location in more than one way: -one way per type, specifically. This is known as -`type punning `_ in -computer programming. There are other ways to achieve that effect as -well, but realize that doing so circumvents the static strong typing -used by Ada to protect us from ourselves and from others. Use it with -care! +For example, let's say we want to query the addresses of some objects +because we are calling the imported C "memcpy" function. That function +requires two addresses to be passed to the call: one for the source, and +one for the destination. We can use the :ada:`'Address` attribute to get +those values. -For a concrete example, imagine a sensor providing a stream of bytes as -input, and you know that groups of those bytes comprise a composite -value, i.e., a value of some record type. (Maybe it is a scalar value -and a checksum.) The buffered array of input bytes would be declared in -terms of a numeric type, probably an unsigned integer :ada:`Byte` type, -whereas the composite type would, of course, be the record type. The -clients of the sensor driver call a driver-defined procedure to get the -latest value, passing an output argument of the record type. The -procedure gets the next available buffered bytes, converts them to the -record type and puts the value in the parameter, maybe checks it for -validity and so on, and then the procedure returns. An overlay would -make that simple. We would declare an object of the record type located -at the same address in the buffer as the starting byte representing the -next composite value. - -But there's a problem: inside the procedure we want to overlay our -record object onto the address of one of the bytes in the buffer. But we -didn't declare the buffer ourselves, at a known location. It is passed -to the procedure as an argument. Therefore, when writing the procedure -we don't already know the address of the buffer, nor the addresses of -the bytes within. As a result, we don't know the address to use in our -overlay. - -Of course, there is a direct solution. We can query the addresses of -objects, and other things too, but objects, especially variables, are -the most common case. In particular, we can say :ada:`X'Address` to -query the starting address of object :ada:`X`, including the case where -:ada:`X` is a component of an array. With that information we know what -address to specify for our record object overlay. - -Here's the generic procedure body again, now showing the overlay that -both specifies and queries an address: +We will explore importing routines and objects implemented in other +languages elsewhere. For now, just understand that we will have an Ada +declaration for the imported routine that tells the compiler how it +should be called. Let's assume we have an Ada function declared like so: .. code-block:: ada - procedure Retrieve_4_Bytes - (Value : out Retrieved; - Buffer : Byte_Array; - Start : Index) - is - Buffer_Overlay : Retrieved with Address => Buffer (Start)'Address; + function MemCopy + (Destination : System.Address; + Source : System.Address; + Length : Natural) + return Address + with + Import, + Convention => C, + Link_Name => "memcpy", + Pre => Source /= Null_Address and then + Destination /= Null_Address and then + Source /= Destination and then + not Overlapping (Destination, Source, Length), + Post => MemCopy'Result = Destination; + -- Copies Length bytes from the object designated by Source to the object + -- designated by Destination. + +The three aspects that do the importing are specified after the reserved +word :ada:`with` but can be ignored for this discussion. We'll talk +about them later. The preconditions make explicit the otherwise implicit +requirements for the arguments passed to memcpy, and the postcondition +specifies the expected result returned from a successful call. Neither +the preconditions nor the postconditions are required for importing +external entities but they are good "guard-rails" for using those +entities. If we call it incorrectly the precondition will inform us, and +likewise, if we misunderstand the result the postcondition will let us +know (at least to the extent that the return value does that). + +For a sample call to our imported routine, imagine that we have a +procedure that copies the bytes of a String parameter into a Buffer +parameter, which is just a contiguous array of bytes. We need to tell +MemCopy the addresses of the arguments passed so we apply the attribute +accordingly: + +.. code-block:: ada + + procedure Put (This : in out Buffer; Start : Index; Value : String) is + Result : System.Address with Unreferenced; begin - ... - Value := Buffer_Overlay; - if Default_Bit_Order /= High_Order_First then -- we're not on a Big Endian machine - GNAT.Byte_Swapping.Swap4 (Value'Address); - end if; - end Retrieve_4_Bytes; + Result := MemCopy (Destination => This (Start)'Address, + Source => Value'Address, + Length => Value'Length); + end Put; + +The order of the address parameters is easily confused so we use the +named association format for specifying the actual parameters in the +call. -When we declare the :ada:`Buffer_Overlay` object we also specify its -address |mdash| strictly speaking its starting address |mdash| which, -not surprisingly, is the address of the :ada:`Buffer` component at index -:ada:`Start`. Applying the :ada:`Address` attribute gives us that -location. +Although we assign Result we don't otherwise use it, so we tell the +compiler this is not a mistake via the :ada:`Unreferenced` aspect. And if we +do turn around and reference it the compiler will complain, as it should. -There are other characteristics we might want to query too. For example, -we might want to ask the compiler what alignment it chose for a -given object (or type, for all such objects). Rather than describe all -the possibilities, we can just say that all the representation -characteristics that can be specified can also be queried. We cover -specifying representation characteristics next, so just assume the -corresponding queries are available. +(We don't show the preconditions for Put, but they would have specified +that Start must be a valid index into This particular Buffer, and that +there must be room in the Buffer argument for the number of bytes in +Value when starting at the Start index, so that we don't copy past the +end of the Buffer argument.) + +There are other characteristics we might want to query too. + +We might want to ask the compiler what alignment it chose for a given +object (or type, for all such objects). For a type, an Alignment of zero +means that objects of the type are not normally aligned on a storage +element boundary at all. That could happen if the type is packed down +into a composite object, such as an array of Booleans. We'll discuss +"packing" soon. More commonly, the smallest likely value is 1, meaning +that any storage element's address will suffice. If the machine has no +particular natural alignments, then all type Alignments will probably +be 1 by default. That would be somewhat rare today, though, because +modern processors usually have comparatively strict alignment requirements. + +We can ask for the amount of storage associated with various entities. +For example, when applied to a task, :ada:`'Storage_Size` tells us the +number of storage elements reserved for the task's execution. The value +includes the size of the task's stack, if it has one. We aren't told if +other required storage, used internally in the implementation, is also +included in this number. Often that other storage is not included in +this number, but it could be. + +:ada:`Storage_Size` is also defined for access types. The meaning is a +little complicated. Access types can be classified into those that +designate only variables ("access-to-object") and those that can also +designate constants. Constants are never allocated dynamically so we can +ignore them. Each access-to-object type has an associated storage pool. +The storage allocated by :ada:`new` comes from the pool, and instances +of Unchecked_Deallocation return storage to the pool. + +When applied to an access-to-object type, :ada:`Storage_Size` gives us +the number of storage elements reserved for the corresponding pool. + +Note that :ada:`Storage_Size` doesn't tell us how much available, +unallocated space remains in a pool. It includes both allocated and +unallocated space. Note, too, that although each access-to-object type +has an associated pool, that doesn't mean that each one has a distinct, +dedicated pool. They might all share one, by default. On an operating +system, such as Linux, the default shared pool might even be implicit, +consisting merely of calls to the OS routines in C. + +As a result, querying :ada:`Storage_Size` for access types and tasks is +not necessarily all that useful. Specifying the sizes, on the other hand, +definitely can be useful. + +That said, we can create our own pool types and define precisely how +they are sized and how allocation and deallocation work, so in that case +querying the size for access types could be more useful. + +For an array type or object, :ada`'Component_Size` provides the size in +bits of the individual components. + +More useful are the following two attributes that query a degree +of memory sharing between objects. + +Applied to an object, :ada`'Has_Same_Storage` is a Boolean function that takes +another object of any type as the argument. It returns whether the two +objects' representations occupy exactly the same bits. If the +representation is contiguous, the objects sit at the same address and +occupy the same length of memory. + +Applied to an object, :ada`'Overlaps_Storage` is a Boolean function that takes +another object of any type as the argument. It returns whether the two +objects' representations share at least one bit. + +Generally, though, we specify representation characteristics far more +often than we query them. Rather than describe all the possibilities, we +can just say that all the representation characteristics that can be +specified can also be queried. We cover specifying representation +characteristics next, so just assume the corresponding queries are +available. That said, there is one particular representation query we need to talk about explicitly, now, because there is a lot of confusion about it: the @@ -442,38 +514,45 @@ that :ada:`'Size` returns values in terms of **bits**. If we apply :ada:`'Size` to a type, the resulting value depends on the kind of type. -For scalar types, i.e., those without components such as integers and -enumerations, the attribute returns the *minimum* number of bits -required to represent all the values of the type. Consider type :ada:`Boolean`, -which has two possible values. One bit will suffice, and indeed the -language standard requires :ada:`Boolean'Size` to be the value 1. +For scalar types, the attribute returns the *minimum* number of bits +required to represent all the values of the type. Here's a diagram +showing what the category "scalar types" includes: + +.. image:: images/scalar_types_tree.png.png + :width: 600 + :alt: Scalar types tree + +Consider type :ada:`Boolean`, which has two possible values. One bit +will suffice, and indeed the language standard requires +:ada:`Boolean'Size` to be the value 1. This meaning also applies to subtypes, which can constrain the number of values for a scalar type. Consider subtype :ada:`Natural`. That's a subtype -defined by the language to be type :ada:`Integer` but with a range of -:ada:`0 .. Integer'Last`. +defined by the language to be type :ada:`Integer` but with a range of +:ada:`0 .. Integer'Last`. On a 32-bit machine we would expect Integer to be a native type, and thus 32-bits. On such a machine if we say :ada:`Integer'Size` we will indeed get 32. But if we say :ada:`Natural'Size` we will get 31, not 32, because only 31 bits are needed to represent that range on that machine. -The size of objects, on the other hand, cannot be just a matter of the -possible values. Consider type :ada:`Boolean` again, where :ada:`Boolean'Size` -is required to be 1. No compiler is likely to allocate one bit to a :ada:`Boolean` -variable, because typical machines don't support -individually-addressable bits. Instead, addresses refer to storage -elements, of a size indicated by the :ada:`Storage_Unit` constant. The compiler -will allocate the smallest number of storage elements necessary, -consistent with other considerations such as alignment. Therefore, for a -machine that has :ada:`Storage_Unit` set to a value of eight, we can -assume that a compiler for that machine will allocate an entire eight-bit -storage element to a stand-alone :ada:`Boolean` variable. The other seven bits -are simply not used by that variable. Moreover, those seven bits are not -used by any other stand-alone object either, because access would be far -less efficient, and such sharing would require some kind of locking to -prevent tasks from interfering with each other when accessing those -stand-alone objects. (Stand-alone objects are independently addressable; -they wouldn't stand alone otherwise.) +The size of objects, on the other hand, cannot be just a matter of the +possible values. Consider type :ada:`Boolean` again, where +:ada:`Boolean'Size` is required to be 1. No compiler is likely to +allocate one bit to a :ada:`Boolean` variable, because typical machines +don't support individually-addressable bits. Instead, addresses refer to +storage elements, of a size indicated by the :ada:`Storage_Unit` +constant. The compiler will allocate the smallest number of storage +elements necessary, consistent with other considerations such as +alignment. Therefore, for a machine that has :ada:`Storage_Unit` set to +a value of eight, we can assume that a compiler for that machine will +allocate an entire eight-bit storage element to a stand-alone +:ada:`Boolean` variable. The other seven bits are simply not used by +that variable. Moreover, those seven bits are not used by any other +stand-alone object either, because access would be far less efficient, +and such sharing would require some kind of locking to prevent tasks +from interfering with each other when accessing those stand-alone +objects. (Stand-alone objects are independently addressable; they +wouldn't stand alone otherwise.) By the same token (and still assuming a 32-bit machine), a compiler will allocate more than 31 bits to a variable of subtype Natural because @@ -487,7 +566,7 @@ with most implementations, because accessing the components at run-time would require more machine instructions. We'll go into the details of that later. -Let's talk further about types. +Let's talk further about sizes of types. For record types, :ada:`'Size` gives the minimum number of bits required to represent the whole composite value. But again, that's not @@ -513,17 +592,17 @@ layout for that record type assuming the compiler does not reorder the components. Observe that some bytes allocated to objects of type :ada:`R` are unused (the darkly shaded ones). In this case that's because the alignment of subtype :ada:`S` happens to be 4 on this machine. The component -:ada:`X` of that subtype :ada:`S` cannot start at byte offset 1, or 2, or 3, -because those addresses would not satisfy the alignment constraint of +:ada:`X` of that subtype :ada:`S` cannot start at byte offset 1, or 2, or 3, +because those addresses would not satisfy the alignment constraint of :ada:`S`. (We're assuming byte 0 is at a word-aligned address.) Therefore, :ada:`X` starts at the object's starting address plus 4. Components -:ada:`B` and :ada:`C` are of types that have an alignment of 1, so they +:ada:`B` and :ada:`C` are of types that have an alignment of 1, so they can start at any storage element. -They immediately follow the bytes allocated to component :ada:`X`. -Therefore, :ada:`R'Size` is 80, or 10 bytes. The three bytes following +They immediately follow the bytes allocated to component :ada:`X`. +Therefore, :ada:`R'Size` is 80, or 10 bytes. The three bytes following component :ada:`M` are simply not used. -But what about the two bytes following component :ada:`C`? They could be +But what about the two bytes following the last component :ada:`C`? They could be allocated to stand-alone objects if they would fit. More likely, though, the compiler will allocate those two bytes to objects of type :ada:`R`, that is, 12 bytes instead of 10 are allocated. As a result, 96 bits are @@ -539,7 +618,7 @@ To make that work, the compiler takes the most stringent alignment of all the record type's components and uses that for the alignment of the overall record type. That way, any address that satisfies the record object's alignment will satisfy the components' alignment requirements. -The alignment is component :ada:`X`, of subtype :ada:`S`, is 4. The other +The alignment is component :ada:`X`, of subtype :ada:`S`, is 4. The other components have an alignment of 1, therefore :ada:`R'Alignment` is 4. An aligned address plus 12 will also be an aligned address. @@ -547,8 +626,7 @@ This rounding up based on alignment is recommended behavior for the compiler, not a requirement, but is reasonable and typical among vendors. Although it can result in unused storage, that's the price paid for speed of access (or even correctness for machines that would fault -on misaligned address allocations). - +on misaligned component accesses). As you can see, alignment is a critical factor in the sizes of composite objects. If you care about the layout of the type you very likely need @@ -568,7 +646,7 @@ Now :ada:`R'Size` will report 56 bits instead of 80. The one trailing byte will still be padding, but only that one. What about unbounded types, for example type :ada:`String`? Querying the -:ada:`'Size` in that case would provide an implementation-defined result. +:ada:`'Size` in that case would provide an implementation-defined result. A somewhat silly thing to do, really, since the type |mdash| by definition |mdash| doesn't specify how many components are involved. @@ -579,41 +657,918 @@ sending values over the wire, the code should query the size of the *parameter* holding the value to be sent. That will tell you how many bits are really needed. -One last point. GNAT, and now Ada 202x, define an attribute named -:ada:`Object_Size`. It does just what the name suggests: what :ada:`'Size` does -when applied to objects rather than types. GNAT also defines another attribute, -named :ada:`Value_Size`, that does what :ada:`'Size` does when applied to -types. The former is far more useful so Ada has standardized it. +One last point. GNAT, and now Ada 202x, define an attribute named +:ada:`Object_Size`. It does just what the name suggests: what +:ada:`'Size` does when applied to objects rather than types. GNAT also +defines another attribute, named :ada:`Value_Size`, that does what +:ada:`'Size` does when applied to types. The former is far more useful +so Ada has standardized it. Specifying Representation ------------------------- +Recall that we said :ada:`Boolean'Size` is required to be 1, and that +stand-alone objects of type Boolean are very likely allocated some +integral number of storage elements (e.g., bytes) in memory, typically +one. What about arrays of Booleans? Suppose we have an array of 16 +Boolean components. How big are objects of the array type? It depends on +the machine. Continuing with our hypothetical (but typical) +byte-addressable machine, for the sake of efficient access each +component is almost certainly allocated an individual byte rather than a +single bit, just like stand-alone objects. Consequently, our array of 16 +Booleans will be reported by :ada:`'Size` to be 128 bits, i.e., 16 +bytes. If you wanted a bit-mask, in which each Boolean component is +allocated a single bit and the total array size is 16 bits, you'd have a +problem. The compiler assumes you want speed of access rather than +storage minimization, and normally that would be the right assumption. -Recall that we said :ada:`Boolean'Size` is always 1. Suppose we have an array -of 16 Boolean components. How big are objects of the type? For the sake -of efficient access, each component is almost certainly allocated an -individual byte rather than a single bit. Our array of 16 Booleans will -be reported by :ada:`'Size` to be 128 bits. If you wanted a bit-mask, in which -each Boolean component is allocated a single, you have a problem. Naturally -there is a solution. +Naturally there is a solution. Ada allows us to specify the +representation characteristics of types, and thus objects of those +types, including their bit-wise layouts. It also allows us to specify +the representation of individual objects. You should understand, though, +that the compiler is not required to do what you ask. That liberty is +unavoidable because you might ask the impossible. If you specify that +the array of 16 Booleans is to be represented completely in 15 bits, +what can the compiler do? Rejecting that specification is the only +reasonable response. But if you specify something possible the compiler +is going to do what you ask, absent some compelling reason to the +contrary. -As you saw earlier, it can pay to consider the component alignments when -declaring the components your record types, because there's no guarantee that the -compiler will optimize (reorder) the layout for you. That said, what you should -really do is specify the actual record layout you want. But when you do, keep -the component alignments in mind. +With that in mind, let's examine setting the size for types. +So, how do we specify that the want our array of 16 Boolean components +to be allocated one bit per component, for a total allocation of 16 +bits? There are a couple of ways, one somewhat better than the other. +First, you can ask that the compiler "pack" the components into as small +a number of bits as it can: -.. todo:: +.. code-block:: ada - Complete section! + type Bits16 is array (0 .. 15) of Boolean with + Pack; + +That likely does what you want: Bits16'Size will probably be 16. + +But realize that the Pack aspect (and corresponding pragma) is merely a +request that the compiler do its best to minimize the number of bits +allocated, not necessarily that it do exactly what you expected or +required. + +We could set the size of the entire array type: + +.. code-block:: ada + + type Bits16 is array (0 .. 15) of Boolean with + Size => 16; + +But the language standard says that a Size clause on array and record +types should not affect the internal layout of their components. That's +Implementation Advice, so not normative, but implementations are really +expected to follow the advice, absent some compelling reason. That's +what the Pack aspect, record_representation_clauses, and Component_Size +clauses are for. (We'll talk about record_representation_clauses +momentarily.) That said, at least one other vendor's compiler would have +changed the size of the array type because of the Size clause, so GNAT +defines a configuration pragma named Implicit_Packing that overrides the +default behavior. With that pragma applied, the Size clause would +compile and suffice to make the overall size be 16. That's a +vendor-defined pragma though, so not portable. + +Therefore, the best way to set the size for the array type is to set the +size of the individual components, via the Component_Size aspect as the +Implementation Advice indicates. That will say what we really want, +rather than a "best effort" request for the compiler, and is portable: + +.. code-block:: ada + + type Bits16 is array (0 .. 15) of Boolean with + Component_Size => 1; + +With this approach the compiler must either use the specified size for +each component or refuse to compile the code. If it compiles, objects +of the array type will be 16 bits total. + +Now that we have a bit-mask array type, let's put it to use when +specifying the address of an object. + +Let's say you want to access the individual bits of what is usually +treated as a simple signed integer. One way to do that is to express an +"overlay," in which an object of one type is placed at the same memory +location as a distinct object of a distinct type, thus overlaying one +object over the other. Doing so allows you to interact with that memory +location in more than one way: one way per type, specifically. This is +known as `type punning `_ in +computer programming. There are other ways to achieve that effect as +well, but realize that you're circumventing the static strong typing +used by Ada to protect us from ourselves and from others. Use it with +care! + +.. code-block:: ada + + type Bits32 is array (0 .. 31) of Boolean with + Component_Size => 1; + + X : Integer; + Y : Bits32 with Address => X'Address; + +We can query the addresses of objects, and other things too, but +objects, especially variables, are the most common case. In the above, +we say :ada:`X'Address` to query the starting address of object +:ada:`X`. With that information we know what address to specify for our +bit-mask overlay Y. Now X and Y are aliases, and therefore we can +manipulate those memory bytes as either an integer or as an array of +individual bits. (Note that we could have used a modular type as the +overlay if all we wanted was an unsigned view.) + +It is worth noting that Ada language rules say that for such an overlaid +object the compiler should not perform optimizations that it would +otherwise apply in the absence of aliases. That's necessary, +functionally, but implies degraded performance, so keep it in mind. +Aliasing precludes some desirable optimizations. + +You may be asking yourself how to know that type Integer is 32 bits +wide, so that we know what size array to use for the bit-mask. The +answer is that you just have to know the target well when doing +low-level programming, and that portability is not the controlling +requirement. The hardware becomes much more visible, as we mentioned. + +That said, you could at least verify the assumption: + +.. code-block:: ada + + pragma Compile_Time_Error (Integer'Object_Size /= 32, "Integers expected to be 32 bits"); + X : Integer; + Y : Bits32 with Address => X'Address; + +That's a vendor-defined pragma so this is not fully portable. It isn't +an unusual pragma, though, so at least you can probably get the same +functionality even if the pragma name varies. + +Let's return, momentarily, to setting the size of entities, but now let's +focus on setting the size of objects. + +We've said that the size of an object is not necessarily the same as the +size of the object's type. The object size won't be smaller, but it +could be larger. Why? For a stand-alone object or a parameter, most +implementations will round the size up to a storage element boundary, or +more, so the object size might be greater than that of the type. Think +back to Boolean, where Size is required to be 1, but stand-alone objects +are probably allocated 8 bits, i.e., an entire storage element (on our +hypothetical byte-addressed machine). + +Likewise, recall that numeric type declarations are mapped to underlying +hardware numeric types. These underlying numeric types provide at least +the capabilities we request with our type declarations, e.g., the range +or number of digits, perhaps more. But the mapped numeric hardware type +cannot provide less than requested. If there is no underlying hardware +type with at least our requested capabilities, our declarations won't compile. +That mapping means that specifying the size of a numeric type doesn't +necessarily affect the size of objects of the type. That numeric +hardware type is the size that it is, and is fixed by the hardware. + +For example, let's say we have this declaration: + +.. code-block:: ada + + type Device_Register is range 0 .. 2**5 - 1 with Size => 5; + +That will compile successfully, because there will be a signed integer +hardware type with at least that range. (Not necessarily, legally +speaking, but realistically speaking, there will be such a hardware +type.) Indeed, it may be an 8-bit signed integer, in which case +Device_Register'Size will give us 5, but objects of the type will have a +size of 8, unavoidably, even though we set Size to 5. + +The difference between the type and object sizes can lead to potentially +problematic code: + +.. code-block:: ada + + type Device_Register is range 0 .. 2**8 - 1 with Size => 8; + My_Device : Device_Register + with Address => To_Address (...); + +The code compiles successfully, and tries to map a byte to a hardware +device that is physically connected to one storage element in the +processor memory space. The actual address is elided as it is not +important here. + +That code might work too, but it might not. We might think that +My_Device'Size is 8, and that My_Device'Address points at an 8-bit +location. However, this isn't necessarily so, as we saw with the +supposedly 5-bit example earlier. Maybe the smallest signed integer the +hardware has is 16-bits wide. The code would compile because a 16-bit +signed numeric type can certainly handle the 8-bit range requested. +My_Device'Size would be then 16, and because 'Address gives us the +*starting* storage element, My_Device'Address might designate the +high-order byte of the overall 16-bit object. When the compiler reads +the two bytes for My_Device what will happen? One of the bytes will be +the data presented by the hardware device mapped to the memory. The +other byte will contain undefined junk, whatever happens to be in the +memory cell at the time. We might have to debug the code a long time to +identify that as the problem. More likely we'll conclude we have a +failed device. + +The correct way to write the code is to specify the size of the +object instead of the type: + +.. code-block:: ada + + type Device_Register is range 0 .. 2**8 - 1; + My_Device : Device_Register with + Size => 8, + Address => To_Address (...); + +If the compiler cannot support stand-alone 8-bit objects, the code +won't compile. + +Alternatively, we could change the earlier Size clause on the +type to apply Object_Size instead: + +.. code-block:: ada + + type Device_Register is range 0 .. 2**8 - 1 with Object_Size => 8; + My_Device : Device_Register with + Address => To_Address (...); + +The choice between the two approaches comes down to personal preference. +With either approach, if the implementation cannot support 8-bit +stand-alone objects, we find out that there is a problem at +compile-time. That's always cheaper than debugging. + +You might conclude that setting the Size for a type serves no purpose. +That's not an unreasonable conclusion, given what you've seen, but in +fact there are reasons to do so. However, there are only a few specific +cases so we will save the reasons for the discussions of the specific +cases. + +There is one general case, though, for setting the 'Size of a type. +Specifically, you may want to specify the size that you think is the +minimum possible, and you want the compiler to confirm that belief. This +would be one of the so-called "confirming" representation clauses, in +which the representation detail is what the compiler would have chosen +anyway, absent the specification. You're not actually changing anything, +you're just getting confirmation via whether or not the compiler accepts +the clause. Suppose, for example, that you have an enumeration type with +256 values. For enumeration types, the compiler allocates the smallest +number of bits required to represent all the values, rounded up to the +nearest storage element. (It's not like C, where enums are just named +int values.) For 256 values, an eight-bit byte would suffice, so setting +the size to 8 would be confirming. But suppose we actually had 257 +enumerals, accidentally? Our size clause set to 8 would not compile, and +we'd be told that something is amiss. + +There are other confirming representation clauses as well. Thinking +again of enumeration types, the underlying numeric values are +integers, starting with zero and monotonically increasing from there +up to N-1, where N is the total number of enumeral values. + +For example: + +.. code-block:: ada + + type Commands is (Off, On); + + for Commands use (Off => 0, On => 1); + +As a result, Off is encoded as 0 and On as 1. That specific underlying +encoding is guaranteed by the language, as of Ada 95, so this is just a +confirming representation clause nowadays. But it was not guaranteed in +the original version of the language, so if you wanted to be sure of the +encoding values you would have specified the above. It wasn't confirming +before Ada 95, in other words. + +But let's also say that the underlying numeric values are not what you +want because you're interacting with some device and the commands are +encoded with values other than 0 and 1. Maybe you want to use an +enumeration type because you want to specify all the possible values +actually used by clients. If you just used some numeric type instead and +made up constants for On and Off, there's nothing to keep clients from +using other numeric values in place of the two constants (absent some +comparatively heavy code to prevent that from happening). Better to use +the compiler to make that impossible in the first place, rather than +debug the code to find the incorrect values used. Therefore, we could +specify different encodings, as long as the relative order is maintained +(and no negative values are used): + +.. code-block:: ada + + for Commands use (Off => 2, On => 4); + +Now the compiler will use those encoding values instead of 0 and 1, +transparently to client code. + +Note that the values given are no longer increasing monotonically: +there's a gap. That is OK, in itself. As long as we use the two +enumerals the same way we'd use named constants, all is well. Otherwise, +there is both a storage issue and a performance issue possible. Let's +say that we use that enumeration type as the index for an array type. +Perfectly legal, but how much storage is allocated to objects of this +array type? Enough for two components? Four, with two unused? The answer +depends on the compiler, and is therefore not portable. The bigger the +gaps, the bigger the overall storage difference possible. Likewise, +imagine we have a for-loop iterating over the index values of one of +these array objects. The for-loop parameter cannot be coded by the +compiler to start at 0, clearly, because there is no index (enumeration) +value corresponding to 0. Similarly, to get the next index, the compiler +cannot have the code simply increment the current value. Working around +that takes some extra code, and takes some extra time that would not be +required if we did not have the gaps. + +The performance degradation can be significant compared to the usual +code generated for a for-loop. Some coding guidelines say that you +shouldn't use an enumeration representation clause for this reason, with +or without gaps. Now that Ada has type predicates we could limit the +values used by clients for a numeric type, so an enumeration type is not +the only way to get a restricted set of named, encoded values. + +.. code-block:: ada + + type Commands is new Integer with + Static_Predicate => Commands in 2 | 4; + + On : constant Commands := 2; + Off : constant Commands := 4; + Silly : constant Commands := 3; -- won't compile + +The storage and performance issues bring us back to confirming clauses. +We want the compiler to recognize them as such, so that it can generate +the usual code, thereby avoiding the unnecessary portability and +performance issues. Why would we have such a confirming clause now? It +might be left over from the original version of the language, written +before the Ada 95 change. Some projects have lifetimes of several +decades, after all, and changing the code can be expensive (certified +code, for example). Whether the compiler does recognize confimring +clauses is a feature of the compiler implementation. We can expect a +mature compiler to do so, but there's no guarantee. + +Now let's turn to what is arguably the most common representation +specification, that of record type layouts. + +Recall from the discussion above that Ada compilers are allowed to +reorder record components in physical memory. In other words, the +textual order in the source code is not necessarily the physical order +in memory. That's different from, say, C, where what you write is what +you get, and you better know what you're doing. On some targets a +misaligned struct component access will perform very poorly, or even +trap and halt, but that's not the C compiler's fault. There's no +guarantee that the Ada compiler will reorder the components to avoid +this problem either. Indeed, GNAT did not reorder components until +relatively recently but does now, at least for the more egregious +performance cases. It does this reordering silently, too, although there +is a switch to have it warn you when it does. To prevent reordering, +GNAT defines a pragma named No_Component_Reorder that does what the name +suggests. You can apply it to individual record types, or globally, as a +configuration pragma. But of course because the pragma is vendor defined +it is not portable. + +Therefore, if you care about the record layout in memory, the best +approach is to specify it explicitly. You need to do something, because +if you let the compiler choose, formerly working code might stop working +with a new release of the compiler, and you might only find the source +of the problem by debugging. (Perusing the new features list for new +releases is a good idea.) Fortunately, there is a standard, +language-defined way to specify a record type's layout. + +The record layout specification consists of the storage places for some +or all components, specified with a record representation clause. This +clause specifies the order, position, and size of components (including +discriminants, if any). + +The approach is to first define the record type, as usual, using any +component order you like |mdash| you're about to specify the physical +layout explicitly, in the next step. That said, defining the components +in the same order as in the physical layout is convenient because you +can copy-and-paste the components in that second step, as a start. + +Let's reuse that record type from the earlier discussion: + +.. code-block:: ada + + type My_Int is range 1 .. 10; + + subtype S is Integer range 1 .. 10; + + type R is record + M : My_Int; + X : S; + B : Boolean; + C : Character; + end record; + +The resulting layout is like so, assuming the compiler doesn't reorder +the components: + +.. image:: images/unoptimized-record-component-order.png + :width: 600 + :alt: Memory allocated to a record with unoptimized layout + +As a result, R'Size will be 80 bits (10 bytes), but those last two bytes +will be will be allocated to objects, for an Object_Size of 96 bits (12 +bytes). We'll change that with an explicit layout specification. + +Having declared the record type, the second step consists of defining +the corresponding record representation clause giving the components' +layout. The clause uses syntax that somewhat mirrors that of a record +type declaration. The components' names appear, as in a record type +declaration. But now, we don't repeat the components' types, instead we +give their relative positions within the record, in terms of a relative +offset that starts at zero. We also specify the bits we want them to +occupy within the storage elements starting at that offset. + +.. code-block:: ada + + for R use record + X at 0 range 0 .. 31; -- note the order swap, + M at 4 range 0 .. 7; -- with this component + B at 5 range 0 .. 7; + C at 6 range 0 .. 7; + end record; + +Now we'll get the optimized order, and we'll always get that order, or the +layout specification won't compile in the first place. + +.. image:: images/optimized-record-component-order.png + :width: 600 + :alt: Memory allocated to a record with optimized layout + +R'Size will be 56 bits (7 bytes), but that last padding byte will also +be allocated to objects, so the Object_Size will be 64 bits (8 bytes). + +Notice how we gave each component an offset, after the reserved word +"at". These offsets are in terms of storage elements, and specify their +positions within the record object as a whole. They are relative to the +beginning of the memory allocated to the record object so they are +numbered starting at zero. We want the X component to be the very first +component in the allocated memory so the offset for that one is zero. +The M component, in comparison, starts at an offset of 4 because we are +allocating 4 bytes to the prior component X: bytes 0 through 3 +specifically. M just occupies one storage element so the next component, +B, starts at offset 5. Likewise, component C starts at offset 6. + +An individual component may occupy part of a single storage element, all +of a single storage element, multiple contiguous storage elements, or a +combination of those (i.e., some number of whole storage elements but +also part of another). The bit "range" specifies this bit-specific +layout, per component, by specifying the first and last bits occupied. +The X component occupies 4 complete 8-bit storage elements, so the bit +range is 0 through 31, for a total of 32 bits. All the other components +each occupy an entire single storage element so their bit ranges are 0 +through 7, for a total of 8 bits. + +The text specifying the offset and bit range is known as a +"component_clause" in the syntax productions. Not all components need be +specified by component_clauses, but (not surprisingly) at most one +clause is allowed per component. Really none are required but it would +be strange not to have some. Typically, all the components are given +positions. If component_clauses are given for all components, the +record_representation_clause completely specifies the representation of +the type and will be obeyed exactly by the implementation. + +Components not otherwise given an explicit placement are given positions +chosen by the compiler. We don't say that they "follow" those explicitly +positioned because there's no requirement that the explicit positions +start at offset 0, although it would be unusual not to start there. + +Placements must not make components overlap, except for components of +variant parts, a topic covered elsewhere. You can also specify the +placement of implementation-defined components, as long as you have a +name to refer to them. (In addition to the components listed in the +source code, the implementation can add components to help implement +what you wrote explicitly.) Such names are always attribute +references but the specific attributes, if any, are +implementation-defined. It would be a mistake for the compiler to define +such implicit components without giving you a way to refer to them. +Otherwise they might go exactly where you want some other component to +be placed, or overlap that place. + +The positions (offsets) and the bit numbers must be static, informally +meaning that they are known at compile-time. They don't have to be +numeric literals, though. Numeric constants would work, but literals are +the most common by far. + +Note that the language does not limit support for component clauses to +specific component types. They need not be one of the integer types, in +particular. For example, a position can be given for components that are +themselves record types, or array types. Even task types are allowed as +far as the language goes, although the implementation might require a +specific representation, such as the component taking no bits whatsoever +(:ada:`0 .. -1`). There are restrictions that keep things sane, for +example rules about how a component name can be used within the overall +record layout construct, but not restrictions on the types allowed for +individual components. For example, here is a record layout containing a +String component, arbitrarily set to contain 11 characters: + +.. code-block:: ada + + type R is record + S : String (1 .. 11); + B : Boolean; + end record; + + for R use record + S at 0 range 0 .. 87; + B at 11 range 0 .. 7; + end record; + +Component S is to be the first component in memory in this example, +hence the position offset is 0, for the first byte of S. Next, S is 11 +characters long, or 88 bits, so the bit range is 0 .. 87. That's 11 +bytes of course, so S occupies storage elements 0 .. 10. Therefore, the +next component position must be at least 11, unless there is to be a +gap, in which case it would be greater than 11. We'll place B +immediately after the last character of S, so B is at storage element +offset 11 and occupying all that one byte's bits. + +We'll have more to say about record type layouts but first we need to talk +about alignment. + +Modern target architectures are comparatively strict about the address +alignments for some of their types. If the alignment is off, an access +to the memory for objects of the type can have highly undesirable +consequences. Some targets will experience seriously degraded +performance. On others, the target will halt altogether. As you can see, +getting the alignment correct is a low-level, but vital, part of correct +code on these machines. + +Normally the compiler does this work for us, choosing an alignment that +is both possible for the target and also optimal for speed of access. +You can, however, override the compiler's alignment choice using an +attribute definition clause or the :ada:`Alignment` aspect. You can do +so on types other than record types, but specifying it on record types +is typical. Here's our example record type with the alignment specified +via the aspect: + +.. code-block:: ada + + type My_Int is range 1 .. 10; + + subtype S is Integer range 1 .. 10; + + type R is record + M : My_Int; + X : S; + B : Boolean; + C : Character; + end record with + Alignment => 1; + +Alignment values are in terms of storage elements. The effect of the +aspect or attribute clause is to ensure that the starting address of the +memory allocated to objects of the type will be a multiple of the +specified value. + +In fact, whenever we specify a record type layout we really should also +specify the record type's alignment, even though doing so is optional. +Why? The alignment makes a difference in the overall record object's +size. We've seen that already, with the padding bytes: the compiler will +respect the alignment requirements of the components, and may add +padding bytes within the record and also at the end to ensure components +start at addresses compatible with their alignment requirements. The +alignment also affects the size allocated to the record type even when +the components are already aligned. As a result the overall size could +be larger than we want for the sake of space. Additionally, when we pass +such objects to code written in other languages, we want to ensure that +the starting address of these objects is aligned as the external code +expects. The compiler might not chose that required alignment by +default. + +Specifying alignment for record types is so useful that in the first +version of Ada there was no syntax to specify alignment for anything +other than record types (via the obsolete "at mod" clause on record +representation clauses). + +For that reason GNAT provides a pragma named Optimize_Alignment. This is +a configuration pragma that affects the compiler's choice of default +alignments where no alignment is explicitly specified. There is a +time/space trade-off in the selection of these values, as we've seen. +The normal choice tries to balance these two characteristics, but with +an argument to the pragma you can give more weight to one or the other. +The best approach is to specify the alignments explicitly, per type, for +those that require specific alignment values. The pragma has the nice +property of giving general guidance to the compiler for what should be +done for the other types and objects not explicitly specified. + +Now let's look into the details. We'll use a case study for this +purpose, including specifying sizes as well as alignments. + +The code for the case study is as follows. It uses Size clauses to +specify the Sizes, instead of the Size aspect, just to emphasize that +the the clause approach is not obsolete. + +.. code-block:: ada + + package Some_Types is + + type Temperature is range -275 .. 1_000; + + type Identity is range 1 .. 127; + + type Info is record + T : Temperature; + Id : Identity; + end record; + + for Info use record + T at 0 range 0 .. 15; + Id at 2 range 0 .. 7; + end record; + + for Info'Size use 24; + + type List is array (1 .. 3) of Info; + for List'Size use 24 * 3; + + end Some_Types; + +When we compile this, the compiler will complain that the size for +"List" is too small, i.e., that the minimum allowed is 96 bits instead +of the 72 we specified. We specified 24 * 3 because we said the record +size should be 24 bits, and we want our array to contain 3 record +components of that size, so 72 seems right. + +What's wrong? As we've shown earlier, specifying the record type size +doesn't necessarily mean that objects (in this case array components) +are that size. The object size could be bigger than we specified for the +type. In this case, the compiler says we need 96 total bits for the +array type, meaning that each of the 3 array components is 32 bits wide +instead of 24. + +Why is it 32 bits? Because the alignment for Info is 2 (on this +machine). The record alignment is a multiple of the largest +alignment of the enclosed components. The alignment for type Temperature +(2), is larger than the alignment for type Identity (1), therefore the +alignment for the whole record type is 2. We need to go from that number +of storage elements to a number of bits for the size. + +Here's where it gets subtle. The alignment is in terms of storage +elements. Each storage element is of a size in bits given by +System.Storage_Unit. We've said that on our hypothetical machine +Storage_Unit is 8, so storage elements are 8 bits wide on this machine. +Bytes, in other words. Therefore, to get the required size in bits, we +have to find a multiple of the two 8-bit bytes (specified by the +alignment) that has at least the number of bits we gave in the Size +clause. Two bytes only provides 16 bits, so that's not big enough, we +need at least 24 bits. The next multiple of 2 bytes is 4 bytes, +providing 32 bits, which is indeed larger than 24. Therefore, the +overall size of the record type, consistent with the alignment, is 4 +bytes, or 32 bits. That's why the compiler says each array component is +32 bits wide. + +But for our example let's say that we really want to use only 72 total +bits for the array type (and that we want three array components). +That's the size we specified, after all. So how do we get the record +type to be 24 bits instead of 32? Yes, you guessed it, we change the +alignment for the record type. If we change it from 2 to 1, the size of +24 bits will work. Adding this Alignment clause line will do that: + +.. code-block:: ada + + for Info'Alignment use 1; + +An alignment of 1 means that any address will work, assuming that +addresses refer to entire storage elements. (An alignment of 0 would +mean that the address need not start on a storage element boundary, but +we know of no such machines.) + +We can even entirely replace the Size clause with the Alignment clause, +because the Size clause specifying 24 bits is just confirming: it's the +value that 'Size would return anyway. The problem is the object size. + +Now, you may be wondering why an alignment of 1 would work, given that +the alignment of the Temperature component is 2. Wouldn't it slow down +the code, or even trap? Well, maybe. It depends on the machine. If it +doesn't work we would just have to use 32 bits for the record type, with +the original alignment of 2, for a larger total array size. + +We said earlier that there are only a small number of reasons to specify +:ada:`'Size` for a type. We can mention one of them now. Setting 'Size +can be useful to give the minimum number of bits to use for a component +of a packed composite type, that is, within either a record type or an +array type that is explicitly packed via the aspect or pragma Pack. It +says that the compiler, when giving its best effort, shouldn't compress +components of the type any smaller than the number of bits specified. +No, it isn't earth-shattering, but other uses are more valuable, to be +discussed soon. + +One thing we will leave unaddressed (pun intended) is the question of +bit ordering and byte ordering within our record layouts. In other +words, the "endian-ness". That's a subject beyond the scope of this +course. Suffice it to say that GNAT provides a way to specify record +layouts that are independent of the endian-ness of the machine, within +some implementation-oriented limits. That's obviously useful when the +code might be compiled for a different ISA in the future. On the other +hand, if your code is specifically for a single ISA, e.g. Arm, even if +different boards and hardware vendors are involved, there's no need to +be independent of the endian-ness. It will always be the same in that +case. (Those are "famous last words" though.) + +Although specifying record type layouts and alignments are perhaps the +most common representation characteristics expressed, there are a couple +of other useful cases. Both involve storage allocation. + +One useful scenario concerns tasking. We can specify the number of +storage elements reserved for the execution of a task object, or all +objects of a task type. You use the Storage_Size aspect to do so: + +.. code-block:: ada + + task Servo with + Storage_Size => 1 * 1024, + ... + +Or the corresponding pragma: + +.. code-block:: ada + + task Servo is + pragma Storage_Size (1 * 1024); + end Servo; + +The aspect seems textually cleaner and lighter unless you have task +entries to declare as well. In that case the line for the pragma +wouldn't add all that much. That's a matter of personal aesthetics +anyway. + +The specified number of storage elements includes the size of +the task's stack (GNAT does have one, per task). The language does not +specify whether or not it includes other storage associated with the +task used for implementing and managing the task execution. With GNAT, +the extent of the primary stack size is the value returned, ignoring any +other storage used internally in the run-time library for managing the +task. + +The GNAT run-time library allocates a default stack amount to each task, +with different defaults depending on the underlying O.S., or lack +thereof, and the target. You need to read the documentation to find the +actual amount, or, with GNAT, read the code (or just ask for help via +your GNAT account number). + +You would need to specify this amount in order to either increase or +decrease the allocated storage. If the task won't run properly, perhaps +crashing at strange and seemingly random places, there's a decent chance +it is running out of stack space. That might also be the reason if you +have a really deep series of subprogram calls that fails. The correction +is to increase the allocation, as shown above. How much? Depends on the +application code. Try increasing it until it runs properly. Then, +iteratively decrease it a little at a time until it starts to fail +again. Add a little back until it runs, and leave it there. + +Even if the task doesn't seem to run out of task stack, you might want +to reduce it anyway, to the extent possible, because the total amount of +storage on your target might be limited. Some of the GNAT bare-metal +embedded targets have very small amounts of memory available, so much so that +the default task stack allocations would exhaust the memory available +quickly. That's what the example above does: empirical data showed that +the Servo task could run with just 1K bytes allocated, so we reduced it +from the default accordingly. (We specified the size with that +expression for the sake of readability, relative to using literals +directly.) + +Notice we said "empirical data" above. How do we know that we exercised +the task's thread of control exhaustively, such that the arrived-at +allocation value covers the worst case? We don't, not with certainty. If +we really must know the allocation will suffice for all cases, say +because this is a high-integrity application, we would use GNATstack. +GNATstack is an offline tool that exploits data generated by the +compiler to compute worst-case stack requirements per subprogram and per +task. As a static analysis tool, its computation is based on information +known at compile time. It does not rely on empirical run-time +information. + +The other useful scenario for allocating storage concerns access types, +specifically access types whose values designate objects, as opposed to +designating subprograms. (Remember, objects are either variables or +constants.) There is no notion of dynamically allocating procedures and +functions in Ada so access-to-subprogram types are not relevant here. +But objects can be of protected types (or task types), and protected +objects can "contain" entries and protected subprograms, so there's a lot +of expressive power available. You just don't dynamically +allocate procedures or functions as such. + +First, a little background. + +By default, the implementation chooses a standard storage pool for each +access-to-object type. The storage allocated by an allocator (i.e., +:ada:`new`) for a given access-to-object type comes from the associated +pool. + +Several access types can share the same pool. By default, the +implementation might choose to have a single global storage pool, used +by all such access types. This global pool might consist merely of calls +to operating system routines (e.g., "malloc"), or it might be a +vendor-defined pool instead. Alternatively, the implementation might +choose to create a new pool for each access-to-object type, reclaiming +the pool's memory when the access type goes out of scope (if ever). +Other schemes are possible. + +Finally, users may define new pool types, and may override the choice of +pool for an access-to-object type by specifying :ada:`Storage_Pool` for +the type. In this case, allocation (via :ada:`new`) takes memory from +the user-defined pool and deallocation puts it back into that pool, +transparently. + +With that said, here's how to specify the storage to be used for an +access-to-object type. There are two ways to do it. + +If you specify :ada:`Storage_Pool` for an access type, you indicate a +specific pool object to be used (user-defined or vendor-defined). The +pool object determines how much storage is available for allocation via +:ada:`new` for that access type. + +Alternatively, you can specify :ada:`Storage_Size` for the access type. +In this case, an implementation-defined pool is used for the access +type, and the storage available is at least the amount requested, maybe +more (it might round up to some advantageous block size, for example). +If the implementation cannot satisfy the request, Storage_Error is +raised. + +It should be clear that that the two alternatives are mutually +exclusive. Therefore the compiler will not allow you to specify both. + +Each alternative has advantages. If your only concern is the total +number of allocations possible, use :ada:`Storage_Size` and let the +implementation do the rest. However, maybe you also care about the +behavior of the allocation and deallocation routines themselves, beyond +just providing and returning the storage. In that case, use +:ada:`Storage_Pool` and specify a pool object of the appropriate type. +For example, you (or the vendor, or someone else) might create a pool +type in which the allocation routine performs in constant time, because +you want to do :ada:`new` in a real-time application where +predictability is essential. + +Lastly, an idiom: when using :ada:`Storage_Size` you may want to specify +a value of zero. That means you intend to do no allocations whatsoever, +and the compiler complain if you try. Why would you want an access type +that doesn't allow dynamically allocating objects? It isn't as +unreasonable as it might sound. If you plan to use the access type +strictly with aliased objects, never doing any allocations, you can have +the compiler enforce your intent. There are application domains that +prohibit dynamic allocations due to the difficulties in analyzing their +behavior, including issues of fragmentation and exhaustion. Access types +themselves are allowed. You'd simply use them to designate aliased +objects alone. In addition, in this usage scenario, if the +implementation associates an actual pool with each access type, the +pool's storage would be wasted since you never intend to allocate any +storage from it. Specifying a size of 0 tells the implementation not to +waste that storage. + +We didn't mention :ada:`Storage_Size` when we covered querying +representation choices, earlier. It is worth mentioning, however, now +that you know about how things work under the hood. + +When you have specified :ada:`Storage_Size` for some access-to-object +type, querying it just returns the number you specified. + +Alternatively, if :ada:`Storage_Pool` and a pool object was specified, +querying :ada:`Storage_Size` returns the value indicated by the pool +object you specified. + +Finally, if neither :ada:`Storage_Pool` nor :ada:`Storage_Size` were +specified, the meaning of querying :ada:`Storage_Size` is +implementation-defined. There are many choices possible when you leave +it up to the implementation, so only the implementation can say what the +query would mean. + +Before we end this section, there is a GNAT compiler switch you should +know about. Th ``-gnatR?`` switch instructs the compiler to list the +representation details for the types, objects and subprograms in the +compiled file(s). Both implementation-defined and user-defined +representation details are presented. The '?' is just a placeholder and +can be one of the following characters: + + ``[0|1|2|3|4][e][j][m][s]`` + +Increasing numeric values provide increasing amounts of information. The +default is '1' and usually will suffice. See the GNAT User's Guide for +Native Platforms for the details of the switch in section 4.3.15 +Debugging Control, available online here: + +https://docs.adacore.com/live/wave/gnat_ugn/html/gnat_ugn/gnat_ugn/building_executable_programs_with_gnat.html#debugging-control + +You'll have to scroll down some to find that specific switch but it is +worth finding and remembering. When you cannot understand what the +compiler is telling you about the representation of something, this +switch is your best friend. Unchecked Programming --------------------- +[setting object and type size larger than required for the sake of unchecked conversion (eg our Commands enum type will only be 8 bits wide, and maybe we want it to be the size of an int so we can pass it to C), +and the other two reasons...] + .. todo:: Complete section! + + +Data Validity +------------- + +.. todo:: + + Complete section! +