


                                                       Chapter 30
                                       MACHINE DEPENDENT FEATURES


In this chapter we will cover some of the constructs available with
Ada that give you the ability to get into real trouble, because we
will be using the low level features of Ada.  The low level
features are those that allow us to get down to the inner workings
of the computer, but we will be able to get to them by use of
rather high level Ada abstractions.


OVERRIDING COMPILER DEFAULTS
_________________________________________________________________

Normally, the compiler will make many decisions for us about how
to store data, and how to operate on it.  Occasionally, we wish to
tell the compiler that we are not satisfied with the way it
defaults something, and we wish for it to use a different means of
representation.  The topics examined in this chapter will give us
the ability to tell the compiler how to map something onto the
underlying hardware.  We gain control of how the compiler
represents some things within the machine, but we also may make the
resulting program nonportable.  This can cause many problems if we
ever wish to move our program to another computer.  In addition,
we may affect the size or speed of the resulting code, since the
compiler writer will use a certain method of representing data
because it results in some form of savings on the particular target
machine.


USE REPRESENTATION CLAUSES SPARINGLY
_________________________________________________________________

In general, the use of the representation clauses which will be
discussed in this chapter, should be used very sparingly if they
are used at all.  In any case, it would be best to delay using any
of these constructs until the program is fairly well developed,
because correct operation of the overall program is far more
important than generating tight efficient code.  After the program
is debugged and operating as desired, it is usually a simple matter
to go back and tighten up the size and speed of the most heavily
used sections of code.  You may find that after you get the program
working in a macro sense, it is fast enough and compact enough that
it is not necessary to resort to these techniques.

With all of these words telling you not to use these programming
techniques, we will now take a look at how to use some of them.
Keep in mind however, that when you use these Ada constructs, you
may lose the ability to port your program to another
implementation.  You will also find that the example programs in
this chapter are those that are most likely to cause problems with
your compiler.

                                                        Page 30-1

                          Chapter 30 - Machine Dependent Features


THE REQUIRED PACKAGE NAMED SYSTEM
_________________________________________________________________

According to the LRM, your compiler must have a standard package
named System which must be defined in Appendix F of the
documentation supplied with your compiler.  Section 13.7 of the LRM
contains a minimum list of items that must be defined for you in
the package specification for System.  It would be profitable for
you to spend a few minutes studying the definition of the package
named System in your compiler documentation and the required items
listed in the LRM.  You will find several constants defined there
that you have been using throughout this tutorial, and now you will
know where they came from.



WHAT REPRESENTATION CONTROLS ARE THERE?
_________________________________________________________________

When we attempt to control the representation of data and the way
it is stored, we are actually telling the Ada compiler how to
define a type.  It would be a little more precise to say we are
telling the compiler how to modify its default method of storing
a certain type, including which parameters we want it to change,
and what to change them to.

There are four different areas of Ada typing that can be specified
with representation clauses, and we will look at each area in
succession.  They are listed in no particular order as follows.

     Length specification
     Record type representation
     Enumeration type representation
     Address specification

You will note that in each example, we will first declare the basic
type, then we will tell the Ada compiler what parameters we wish
to modify to suit our purposes, and finally we will declare objects
of the modified type.  We will mention this order again in some of
the following example programs.



THE LENGTH SPECIFICATION
_________________________________________________________________

The length specification is used to declare how  ================
many bits can be used to store data in a certain   SMALLINT.ADA
type.  This representation clause is illustrated ================
in the example program named SMALLINT.ADA, which
you should examine at this time.



                                                        Page 30-2

                          Chapter 30 - Machine Dependent Features

The only code that is different in this program is found in lines
9 through 12.  First we define a constant of value 1 named BITS to
be used later for a very good reason.  Next we declare a derived
type which covers a range of -25 through 120, a range small enough
to be represented with only 8 bits.  Since we wish to declare a
rather large array of this type, and we suspect that our compiler
will simply assign a full word of 16 bits or more to each variable
of this type, we tell the compiler that we want it to use only 8
bits to store a variable of this type.  Line 11 is a representation
clause to do this.  It begins with the reserved word for followed
by the type with a tick and the word SIZE.  It looks like an
attribute, and that is just what it is, because we are telling the
compiler that we want the attribute named SIZE to have the value
of 8.

The use of the constant should now be clear.  It makes the
expression extremely clear because when you read the expression it
says just what it is doing.  You should not be bothered that using
this construct will slow down the program, because it will not.
This constant will be evaluated only once, and that will be at
compile time, not when the program is executing.



YOUR COMPILER MAY NOT LIKE THIS CLAUSE
_________________________________________________________________

The LRM does not require that an Ada compiler implement every
representation clause.  For this reason, even though you have a
validated Ada compiler, it may not implement line 11.  If it
doesn't, it will give you a message during compilation that it
cannot handle this representation clause and will fail to give you
an object module.  You will be required to remove the offending
representation clause and recompile the program.  If your compiler
does accept it, when you execute this program you will see that the
type SMALL_INTEGER requires only 8 bits of storage.  After
successfully compiling and running the program, comment out line
11 and compile and execute it again.  You will probably find that
your compiler requires more than 8 bits to store data of this type.
If your compiler cannot handle line 11, comment it out and compile
and execute the resulting program.

Remember that at the beginning of this chapter we stated that of
all the programs in this tutorial, this chapter would contain the
ones that were most likely to have problems with your compiler.
This is the reason that so many of these programs are not
transportable from compiler to compiler.  Only three of the five
compilers used to test these example programs implemented this
particular clause.






                                                        Page 30-3

                          Chapter 30 - Machine Dependent Features

THE STORAGE_SIZE REPRESENTATION CLAUSE
_________________________________________________________________

Another representation clause that is very similar to SIZE is the
one named STORAGE_SIZE.  This is used to tell the compiler how many
storage units to use to store an access type variable or a task
type variable.  The LRM is not very specific on just what a storage
unit is, so it must be defined by your compiler.  Because it is not
well defined, and is therefore different for each compiler, an
explanation may be more confusing than simply not attempting to
explain it.  You will be left to study it on your own, remembering
that it is similar to SIZE.  With all of the Ada you have studied
to this point, you should be able to easily decipher the notes on
this topic in your compiler documentation.



THE RECORD TYPE REPRESENTATION
_________________________________________________________________

Examine the program named BITFIELD.ADA for an    ================
example of two additional low level constructs,    BITFIELD.ADA
the record type representation and the unchecked ================
conversion.  We will begin with the record type
representation.

First, we declare a record type named BITS with three fields of
extremely limited range since we only wish to store one or two bits
in each field.  Because of the limited range, we would like to
instruct the compiler to store the individual variables in very
small memory units, and in addition, we would like it to store all
three fields in a single word.  We do this in lines 16 through 20
where we give the compiler the desired pattern for the three
fields.  It looks very much like a record except for the
substitution of the reserved words for and use in place of type and
is in the record type definition.  Remember that we are modifying
the type we have already declared to tell the compiler how to
actually implement it.

Each of the fields is slightly different also since the reserved
word at is used followed by the number 0 in all three cases.  This
is telling the system to store this variable in the word with an
offset of zero from the first word, or in other words, we are
telling the compiler to put this variable in the first word of the
record.  Also, after the reserved word range, we have another
defined range which tells the compiler which bits of the word to
store these in.  The variable named Lower is therefore to be stored
in the first word, and is to occupy bit positions 0 and 1 of that
word.  The variable named Middle is also to be stored in the first
word, and will occupy bit position 2 of that word.  The variable
named High will occupy bit positions 3 and 4 of the same word.




                                                        Page 30-4

                          Chapter 30 - Machine Dependent Features

WHAT DO THE BIT POSITIONS MEAN?
_________________________________________________________________

By this point, you are probably wondering what is bit position 0.
Is it the least significant bit or the most significant bit?  That
question is entirely up to the compiler writer and you must consult
your documentation for the answer to this question and nearly any
other questions you may have about this new construct.  One
possible resulting bit pattern is illustrated in figure 30-1.  The
actual bit pattern for your compiler may be something entirely
different.

The INTEGER type variable consists of a 16 bit number on nearly
every microcomputer application and some minicomputers, but even
this is up to the implementor to define in any way he desires.
Because 16 bits is fairly standard for the INTEGER type variable,
and for a single word, the various fields of the record were
declared to be in one word to illustrate the next low level
programming construct in Ada.  If your compiler is especially
smart, you could continue the packing by telling the compiler to
squeeze the entire record into as few as 5 bits, since that is all
that would be needed to actually store the data.  This would be
done using the SIZE representation clause in a manner similar to
the last example program.


THERE IS A LIMIT TO THE MODIFICATIONS
_________________________________________________________________

After declaring the type, then modifying it to suit our purposes,
we declare a variable of the new type in line 22.  Note that it
would be an error to attempt to further modify the type after we
have declared a variable of that type.  This is because it would
allow declaring another variable of the newly modified type which
would in fact be different from the first variable.

If additional fields were added with at 1 in their representation
clauses, they would be put in the word that was at an offset of 1
from the beginning of the record.  This would be the second word
of course.  You can see that it is possible to very carefully
control where and how the data is stored in a record of this data
type.


A PROBLEM GENERATOR, USE WITH CAUTION
_________________________________________________________________

In line 25, we instantiate a copy of the generic function named
Unchecked_Conversion to illustrate its use.  This is a function
that can really get you into trouble, but can be a real time saver
if you need its capability.  In this case, Switch_To_Bits will use
an INTEGER for its source and a record of type BITS as the result
or target.  A call to the function with an INTEGER type variable
as an argument will change the type of the variable and return the

                                                        Page 30-5

                          Chapter 30 - Machine Dependent Features

same value with a new type.  In this case, because the individual
bits are packed into a single word, the data in the INTEGER type
variable is actually split up into the three fields of the record.
The original data, as well as the three fields, are displayed for
a small range of values.  In this case the composite data in the
integer variable is unpacked into the respective fields by the
system.

Note that line 37 could have used the loop index named Index as the
actual parameter since it is legal in Ada to use a
universal_integer in the call.

The only real requirement for use of the unchecked type conversion
is that both structures have the same number of program units or
bits.  The C programmer will recognize this as the union, and the
Pascal programmer should see that this is the same as using a
variant record for type conversion.

Be sure to compile and run this program to see if it really does
what it should.  Your compiler may not implement some or all of
these features, in which case you can only study the result of
execution given at the end of the example program.  We said at the
beginning of this chapter that there would be a few things you may
not be able to do.  Only two of the five compilers tested compiled
this program completely, and only one stored the bits in the
pattern depicted in figure 30-1.  Note that the
Unchecked_Conversion is not optional but required, and all five
compilers tested by the author compiled it properly.


THE PRAGMA NAMED PACK
_________________________________________________________________

Examine the program named PACKITIN.ADA for an    ================
example of use of the pragma named PACK.  This     PACKITIN.ADA
is an instruction to the compiler to pack the    ================
data as tightly as possible with no concern for
how long it will take for the resulting program
to execute.  Three examples of packing are given here, with each
resulting in a more tightly packed composite type.


NORMAL PACKING DENSITY
_________________________________________________________________

Line 10 contains a declaration of a type which only requires 6 bits
to store, but will probably require a full word of 16 bits or more
on most implementations.  Lines 12 through 15 declare a record that
may require 4 words because of alignment requirements in some
compilers, and line 17 may even waste a few more words due to
alignment considerations.  Lines 43 through 53 are used to output
the sizes of these three types for study.  The results are given
for two rather inexpensive Ada compilers used with MS-DOS, running
on an IBM-PC type microcomputer.

                                                        Page 30-6

                          Chapter 30 - Machine Dependent Features


SOME PACKING TAKES PLACE
_________________________________________________________________

In line 19 the same type is repeated with a different name, and is
used in the record in lines 21 through 25 where it is packed using
the pragma PACK.  Note that the packing only takes place at the
record level, not at the lower level which is assumed to be the
default packing density.  In lines 27 and 28, the same array is
declared with the pragma PACK used again at this level to achieve
a better packing density than the previous example.  The results
of execution illustrate that compiler 1 did a little better at
packing than compiler 2 did.


HIGHER PACKING DENSITY
_________________________________________________________________

Lines 30 through 40 illustrate an even higher packing density
because of the representation clause in line 31 where we instruct
the compiler to use only 8 bits for the type used as elements in
the composite types.  In this case, neither compiler supported the
representation clause, so line 31 had to be commented out resulting
in no additional packing density.


WHICH COMPILER IS BEST?
_________________________________________________________________

Simply because one compiler did a better packing job does not make
it best between the two compared.  There is a penalty to be paid
here when it comes to executing the code because the data fields
are not located in exactly the same places for "normal" or unpacked
data fields.  The compiler that packed the code very efficiently
may take considerably longer to execute a program with data stored
in this way than the other.  The important thing to remember is
that the two compilers, even though both are validated, handled the
types slightly differently.

Keep in mind that the pragma named PACK only packs the data at the
level at which it is mentioned.  It does not pack the data at lower
levels unless it is mentioned there also.  Three of the five
compilers were able to compile this program completely and
correctly.


THE ENUMERATED TYPE REPRESENTATION
_________________________________________________________________

The example program named ENUMREP.ADA             ===============
illustrates the use of the representation clause    ENUMREP.ADA
used with the enumerated type variable to define  ===============
the values of the enumerated values.


                                                        Page 30-7

                          Chapter 30 - Machine Dependent Features


In this case we have declared a type named MOTION for use with some
kind of a robot in which we wish to instruct the robot to move any
one of four directions or stop.  A zero indicates a stopped
condition, and the four directions are actually four different bits
of a binary code.  Assuming that there is a different relay or
electronic switch for each direction, we can output a single field
to control the four relays or switches.  The important thing is
that the enumerated value is the pattern we wish to output, so it
can be output directly.

The enumerated type will work in exactly the same manner as any
other enumerated type.  You can take the PRED, SUCC, VAL, or POS,
and they will work the same way as if they were declared in order.
We have only changed the underlying representation of the
enumerated type.  The values must be declared in ascending order
or a compile error will be issued.

Be sure to compile and execute this program to ascertain that it
is acceptable to your compiler.  The enumerated representation
clause is available with three of the five compilers tested.



THE ADDRESS SPECIFICATION
_________________________________________________________________

The address specification is used in such a nebulous way that it
is very difficult to even illustrate its use in a general purpose
program, so a program is not provided.  The general form of its use
is given in the next two lines which represents a fragment of a
program.

     Robot_Port : MOTION;             -- The port to control
                                      --  direction
     for Robot_Port use at 16#0175#;  -- Absolute address of
                                      --  the robot hardware

The first line of this sequence declares a variable named
Robot_Port to be of type MOTION which was declared in the example
program named ENUMREP.ADA.  You will recall that this type was
intended to be used to control the robot's direction.  The second
line tells the Ada compiler that this variable must be assigned the
absolute address of 0175(hexadecimal), because this is where the
output port is located which controls the robot's direction.  The
reserved words for and use at tell the compiler where to locate
this particular variable.

It should now be obvious why it is not practical to write a general
purpose program to illustrate this concept.  The location of a
usable port will be different on every computer, and the means of
addressing the entire memory space can be quite complex in the case
of segmented memory or with some sort of memory management scheme.
Consult the documentation that came with your compiler to find the

                                                        Page 30-8

                          Chapter 30 - Machine Dependent Features

method used with your compiler to address an absolute memory
location.  It will be listed in appendix F of your documentation,
as required by the LRM.



UNCHECKED_DEALLOCATION
_________________________________________________________________

This is also a very low level routine that is mentioned here for
completeness.  Its use has been illustrated previously in this
tutorial in chapters 13 and 23, where it was used to free up space
that had been dynamically allocated.



PROGRAMMING EXERCISES
_________________________________________________________________

1.   Write a program using Unchecked_Conversion to convert an
     INTEGER type variable into a CHARACTER array of as long as
     needed to represent an INTEGER on your system, probably two
     or four elements.  Use the ORD attribute to convert the
     CHARACTER type data to numerical values and display the
     numerical value of the components on the monitor.  This will
     identify the underlying representation of the INTEGER and the
     CHARACTER types.

2.   Repeat exercise 1 to convert the FLOAT type to an array of
     CHARACTER.
























                                                        Page 30-9