Copy Link
Add to Bookmark
Report

Chapter 14 - Machine Dependent Facilities

eZine's profile picture
Published in 
Modula2
 · 30 Jan 2023

PREREQUISITES FOR THIS MATERIAL

Before attempting to understand this material, you should understand the material presented in Part I of this tutorial and a clear understanding of the material on pointers in Part II.

THIS IS WHERE YOU CAN GET INTO TROUBLE

Modula-2 does a good job of insulating you from the underlying peculiarities of your computer due to the strong TYPE checking which it does. It can prevent you from making many kinds of rather stupid blunders simply by forcing you to follow its predefined conventions. There are times, however, when you wish to ignore some of its help and do something that is out of the ordinary. If you had a need to directly interface with some external device, you would need to get down to the nitty gritty of the operating system and do some things that are outside of the realm of normal programming practice. Modula-2 will allow you to do such things but you will pay a price because you take the chance of hopelessly confusing the system.

The principles taught in this chapter can lead you directly into the operating system where you will have more freedom than you would have thought possible with Modula-2, but it will place more responsibility on you. This material is only for the advanced programmer because it will require a knowledge of the inner workings of the computer and the operating system. Nevertheless, it would be good for you, as a student of Modula-2, to at least read this material, examine the example programs, and compile and run them. You will then have a store of knowledge of these things so that you can use them when you need them.

TYPE RELAXATION EXAMPLE

Load and display the program named TYPEREL.MOD for an example of a program with some very unusual type transfer functions.

TYPEREL.MOD 

(* Chapter 14 - Program 1 *)
MODULE TypeRel;

FROM InOut IMPORT WriteCard,WriteLn;

TYPE IntType = ARRAY[1..10] OF INTEGER;
CardType = ARRAY[81..90] OF CARDINAL;
CharType = ARRAY[1..20] OF CHAR;

VAR IntVars : IntType;
CardVars : CardType;
CharVars : CharType;
Index : INTEGER;
Count : CARDINAL;

BEGIN

Count := 10;
Index := INTEGER(Count);
FOR Count := 1 TO 10 DO
IntVars[Count] := INTEGER(Count) + 64;
END;
CardVars[81] := CARDINAL(IntVars[1]);
CardVars := CardType(IntVars);
CharVars := CharType(CardVars);

FOR Index := 81 TO 85 DO
WriteCard(CardVars[Index],8);
END;

WriteLn;

FOR Index := 1 TO 10 DO
Count := ORD(CharVars[Index]);
WriteCard(Count,4);
END;

WriteLn;

END TypeRel.

Note first that three TYPES are defined, each being the same size considering storage requirements. The first TYPE is 10 INTEGERS, the second is 10 CARDINALS, and the third is 20 CHAR variables which requires the same amount of storage as 10 INTEGERS or CARDINALS. The fact that all three TYPES are the same size is very important for what we will do later in the program.

The first thing we do in the program part of the MODULE is to assign a number to "Count", a CARDINAL type variable. In the next line we assign the value of "Count" to "Index" even though they are of different TYPES because we transform the TYPE in the same manner that we did back in Chapter 3 when we studied TRANSFER.MOD but this time we will go a bit farther with the transformations. Actually, we don't need the type transformation here because INTEGER and CARDINAL are assignment compatible.

We load up the INTEGER array "IntVars" with some nonsense data to work with, the data being the series of numbers from 65 to 74, which should be easy for you to ascertain. Then in line 23, we copy one of the array data points to one of the other to illustrate that the type transformation works even on array elements.

NOW FOR THE BIG TYPE TRANSFORMATION

In line 24 of the program we copy the entire field of 10 INTEGER type variables into the array of 10 CARDINAL type variables. The only restriction is that both of the fields must be exactly the same size which these two are. In order to do the transformation, the TYPE of the resulting data area is used in front of the parentheses of the source variables. Line 25 goes a step farther and copies the new CARDINAL data into 20 CHAR type variables, which is permissible because 20 CHAR variables uses the same amount of storage as 10 CARDINAL variables. You could even transform a record made up of several different types into all CHAR variables, or all INTEGERS, or even another completely different record. The only requirement is that both of the groups be exactly the same size.

This may appear to be a really neat thing to be able to do but there are problems that you will find with this new transformation. There are no data conversions done, only type conversions, which means that you may wind up with a real mess trying to decipher just what the transformed data means. In addition, since each compiler may define the various types of data slightly different, your program will not be easily transportable to another computer, or maybe not even to another compiler on the same computer.

Five of the CARDINAL numbers are displayed on the monitor, then 10 of the CHAR numbers are displayed to show you that they really are the same numbers. The order of the numbers are reversed when output as individual bytes because of the way the data is stored in the microprocessor in your computer. This in itself is an indication that there is no data conversion, but only a data copying, byte by byte.

One other rule must be pointed out, you cannot do a data transformation within a function call, but it is simple enough to do the transformation to a dummy variable and use the dummy variable in the function call if that is necessary. This will be illustrated shortly. Compile and run this program after you study it.

WORD AND ADDRESS VARIABLES

Load and display WORDADDR.MOD for an example using some new data types.

WORDADDR.MOD 

(* Chapter 14 - Program 2 *)
MODULE WordAddr;

FROM SYSTEM IMPORT ADR,WORD,ADDRESS;
FROM InOut IMPORT WriteString,WriteCard,WriteLn;

VAR Index : INTEGER;
CardNo : CARDINAL;
Peach : ADDRESS;
MonoVideo[0B000H:0H] : ARRAY[1..4000] OF CHAR;
ColorVideo[0B800H:0H] : ARRAY[1..4000] OF CHAR;

PROCEDURE PrintNumber(DatOut : WORD);
VAR Temp : CARDINAL;
BEGIN
WriteString("The value is ");
Temp := CARDINAL(DatOut);
WriteCard(Temp,4);
WriteLn;
END PrintNumber;

BEGIN
Index := 17;
CardNo := 38;
Peach := ADR(Index); (* Pointer to an INTEGER *)
Peach := ADR(CardNo); (* Pointer to a CARDINAL *)
PrintNumber(Index); (* Called with an INTEGER *)
PrintNumber(CardNo); (* Called with a CARDINAL *)

Peach := 0B000H:1A2H; (* Pointer to Segment:Offset *)

END WordAddr.

In order to get down to the lowest level of the machine, we need these new types, ADR, WORD, and ADDRESS, which must be IMPORTED from the pseudo module SYSTEM. The pseudo module SYSTEM does not exist as an external module as the others do because the kinds of things it does are closely associated with the compiler itself. The designers of Modula-2 have therefore defined this module to make these things available to us.

The new data type "WORD" is compatible with all data types that use a single word for storage, but it is somewhat limited in what you can do with it. It is most useful as the formal argument to a function which can be called with any data type that is contained in one word. In lines 27 and 28, the same procedure is called, once with an INTEGER, and once with a CARDINAL. Since the procedure is designed to handle either, it will print out both numbers by converting them first to CARDINAL using the type transformation in line 17, then calling the output procedures. Once again, the type transformation cannot be done in the procedure call so a temporary variable is used.

A NEW KIND OF POINTER

The variable "Peach" is assigned the type ADDRESS which is also imported from the pseudo module SYSTEM, and is therefore a pointer to any WORD type of variable. Peach can therefore point to an INTEGER or a CARDINAL as is done in lines 25 and 26. The procedure "ADR" returns the address of any WORD type of variable and it too must be IMPORTED from the pseudo module SYSTEM.

ABSOLUTE ADDRESSES

Notice the two strange looking variables in lines 10 and 11. The variable "MonoVideo" is an array of 4000 CHAR type variables but we have forced it to be located at a very specific location in memory, namely at segment=B000(hex) and offset=0000(hex). This is the method provided for you by Modula-2 by which you can force a variable to be at a specific memory location. In this case we have defined the variable to be stored in the locations in memory where the monochrome monitor display is stored so we can store data directly into the monochrome monitor display area.

The variable "ColorVideo" is the same except that the location referenced is that area where the output for a color monitor is stored. You can see that you can gain control over the actual hardware with this capability but it does require a lot of knowledge of the hardware and the operating system.

In the last line of the program the variable "Peach" is assigned the address of a specific location as an illustration only. This is only possible because "Peach" is a variable of type ADDRESS.

It should be clear to you that with these functions, it is possible to do a lot of data shuffling that could not otherwise be done. The next example program will illustrate their use further.

MORE ADDRESSING EXAMPLES

Load and display the program ADRSTUFF.MOD.

ADRSTUFF.MOD 

(* Chapter 14 - Program 3 *)
MODULE AdrStuff;

FROM InOut IMPORT WriteInt, WriteLn;
FROM SYSTEM IMPORT ADR, SIZE, TSIZE, ADDRESS;

TYPE IntArray = ARRAY[1..8] OF INTEGER;
BigArray = ARRAY[1..5] OF IntArray;

VAR Stuff : BigArray;
NeatPoint : ADDRESS;
IncreAmt : CARDINAL;
Index : INTEGER;
Count : INTEGER;
Amount : INTEGER;

BEGIN
(* Load the array with nonsense data *)
FOR Index := 1 TO 8 DO
FOR Count := 1 TO 5 DO
Stuff[Count][Index] := Index + 10 * Count;
END;
END;
(* Perform some simple pointer operations *)
NeatPoint := ADR(Stuff[1][1]);
Index := INTEGER(NeatPoint^);
WriteInt(Index,6);
IncreAmt := TSIZE(IntArray);
NeatPoint := NeatPoint + IncreAmt;
Index := INTEGER(NeatPoint^);
WriteInt(Index,6);
WriteLn;
(* Perform some pointer operations in a loop *)
Count := INTEGER(TSIZE(BigArray)) DIV INTEGER(TSIZE(IntArray));
FOR Index := 1 TO Count DO
NeatPoint := ADR(Stuff[1][1]) +
CARDINAL((Index-1)*(INTEGER(TSIZE(IntArray))));
Amount := INTEGER(NeatPoint^);
WriteInt(Amount,6);
END;

IncreAmt := SIZE(Stuff);
Count := INTEGER(SIZE(NeatPoint));

END AdrStuff.

This program uses the ADDRESS type and adds two new, rather simple functions, SIZE and TSIZE. Actually, these are not completly new since we used the TSIZE function in the chapter on pointers and dynamic allocation. These two functions will return the size in bytes of any variable or of any type. The program on your monitor has several types defined, then several variables, and finally initializes all of the elements of the array "Stuff" to some nonsense data. The really interesting things begin happening at line 25.

The pointer "NeatPoint" is pointed at the first element of the array "Stuff", and its value is dereferenced into "Index". The type transformation is required because the result of the dereferencing is a CARDINAL. The data is written out. Next the size of the type "IntArray" is assigned to the variable "IncreAmt", which should be 8 words or 16 bytes. In line 29 we do some pointer arithmetic by adding the size of the type "IntArray" to the original value of the pointer which should cause it to point to the next row of the array. After dereferencing the pointer and getting its new value, we print it out to find that it did indeed move to the next row of the array.

Based on the above discussion, it should be apparent to you that you can move the pointer all around the array named "Stuff" and get whatever data you wish. The next section uses a loop to continue the process through all five rows. The only thing that may be confusing is line number 34 where we get the size of the "BigArray" type and divide it by the size of the "IntArray" type. The result should be 5, and you will see that it does five iterations through the loop. This is really a dumb way to get through this particular loop but it is only for purposes of illustration that it is done. Notice all of the type transformations to INTEGER in these statements, this is because the functions all return a CARDINAL type of data. Doing all of this in CARDINAL numbers would have made it much cleaner, but this was more illustrative for you.

TWO MORE LINES OF ILLUSTRATION

Lines 42 and 43 are given as an illustration for you of how to use the SIZE function. It simply returns the size in bytes, of any variable used as an argument.

PROGRAMMING EXERCISES

  1. Modify the "AdrStuff" module to print out some of the type and variable sizes such as those calculated in lines 41 and 42.
  2. Write a program with an array of 100 CARDINAL elements, fill the elements with nonsense data, and use a pointer to print out every 12th value starting at the highest element (number 100) and working downward.

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT