Skip to content

PC Assembler (II)

Vector 4:3, page 119

system interfaces performance

Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 119

Introduction: How APL Manages Memory

In a previous paper (VECTOR 4.2), we have run an Assembler-written program from APL. We are going to see how we can have access to APL variables.

The ⎕STPTR function has been evoked in the previous paper. To understand what its use may be, we first have to know how APL manages its memory.

An APL variable is stored in memory in consecutive words. Ahead of the data words, we can see the variable’s descriptor, i.e. its type (character, integer or real), its rank and dimensions. Some more information is present for APL’s use. Considering the vector:

A←⍳5

In memory, we can find the following bytes, where X represents a byte that does not have a useful meaning for us:

2 0  X X X X  2 1  5 0  1 0 2 0 3 0 4 0 5 0  255...
---           ---  ---  -------------------  ------
 a             b    c            d             e

A variable is always stored in a multiple of 16 number of bytes. If positions are not all used, the filling value is 255. The vector X uses 20 bytes, plus 12 filling bytes in the (e) area. Its size is 32 bytes.

The (a) area is equal to the total number of bytes used by A, divided by 16, that is 2 in this case. This area is integer coded, so we have 2 (low half) before 0 (high half).

The (b) area represents the variable’s type: 1 for character, 2 for integer and 8 for real, followed by the rank (1 for this vector).

If the variable is a scalar, its value comes just after the type: in this case, there is no dimension description (c area). In the other cases, we find all dimensions, in the same order as ⍴A. In this example, the variable is a vector, its dimension is 5, so we have 5 and 0. If the variable has one dimension that exeeds 32767 elements, the byte holding the type is 16 plus the normal value, i.e. 17, 19 or 24. Then the dimensions are 2 words long.

The (d) area holds the data bytes, in the order given by ravel A.

Now we now know how a variable is stored, but we still have to know its location in memory!

When APL needs memory, for a variable or function creation, or for an intermediate result, it searches for free memory in high addresses. If the free space is not sufficient, then it reoganizes all the memory, i.e. it suppresses all the erased variables and functions (This is called a ‘garbage collection’). Then it tries again. If the error occurs once more, we receive a WS FULL message.

This process means that a variable address is not steady. The only steady information we can get is ⎕STPTR’s result. A variable’s address is given by:

256 ⊥ ⎕PEEK 1 0 + ⎕STPTR 'VARIABLE'

The result is exact for a few moments, but it may be wrong before when can use it!

So, the parameters we will give to an assembler written routine will not be real addresses, but ⎕STPTR results. This will be sufficient to find the real addresses in the routine, and the APL objects will not move during the Assembler routine execution, while APL is pending. We can calculate an address in Assembler with:

MOV  ES,[BX] ; if BX = ⎕STPTR 'VARIABLE', then ES is the address.

This instruction will work if the DS register has the value corresponding to a ⎕SEG of iota zero. This is the value which is always passed by APL to DS. If you change its value, you have to give the old value before any address search. If you do not remember that, you will have wrong results, or your computer may hang.

Here is the listing I promised you:

 CODE     SEGMENT BYTE
          ASSUME  CS:CODE
; AX = STPTR BINARY VECTOR
; result = ax  =       1=+/binary
BOOL1     PROC     FAR
          NOP
          NOP
          NOP
          NOP
          NOP
          NOP
          NOP
          NOP
          NOP
          NOP
          MOV     BX,AX               ; BX = STPTR A
          MOV     ES,[BX]             ; ES = ADRS A
          MOV     AX,ES:WORD PTR 6    ; A'S RANK AND TYPE
          CMP     AL,2                ; INTEGER ?
          JNE     ERR                 ; ERROR IF NOT
          MOV     DI,10               ; POINTS TO 1RST VALUE
; ES:DI POINTS TO THE BEGINNING OF THE VECTOR
          MOV     CX,ES:WORD PTR 8    ; DIMENSION IF IT'S A VECTOR
          SUB     BX,BX               ; NUMBER OF 1, INITIALIZED TO 0
          CMP     AH,1                ; TAKE A LOOK TO THE RANK
          JE      BOU                 ; GO AHEAD IF IT'S A VECTOR
          JL      OK                  ; OK IF IT IS A SCALAR
ERR:      INT     0                   ; "DOMAIN ERROR" IF RANK > 1
OK:       MOV     CX,1                ; SCALAR, SO 1 ELEMENT
          DEC     DI                  ; POINTER TO THE SCALAR'S VALUE
          DEC     DI
; LOOP ON EACH VECTOR'S ELEMENT
LOOP:     JCXZ    DONE                ; DONE IF IT IS THE LAST ELEMENT
          MOV     AX,1                ; 1 IS THE VALUE WE ARE LOOKING FOR
          REPNE   SCASW               ; GO TO THE NEXT 1
          JNE     DONE                ; END OF VECTOR, THERE IS NO OTHER 1
          INC     BX                  ; WE HAVE SEEN A 1
          REPNE   SCASW               ; GO TO NEXT 1
          JNE     DONE                ; DONE IF NO OTHER 1
          INC     BX                  ; IF WE FIND 1
DONE:     XOR     AX,AX               ; INITIALIZE THE RESULT
          CMP     BX,1                ; THE NUMBER OF 1
          JNE     DDONE               ; IF WE DO NOT HAVE EXACTLY ONE 1
          INC     AX                  ; IF WE HAVE ONE 1
DDONE:    RET                         ; AND RETURN TO APL
BOOL1     ENDP
CODE      ENDS
          END

The routine has to be assembled, linked and translated to binary. We can get the result from APL with:

'BOOL1.BIN'⎕NTIE ¯1
CODE←10↓⎕NREAD ¯1 82,⎕NSIZE ¯1

We need to drop the first 10 elements which correspond to the ten NOP instructions we find in the beginning of the Assembler code. This operation is not always necessary, but it is a good habit to have.

The code is stored, in character format, in APL variable CODE. The same code, in integer format, may be obtained by:

CODE←163 ⎕DR CODE,(2|⍴CODE)⍴⎕AV

Writing the APL function is now an easy operation. We can test this function with vectors of different lengths in order to know if this function is faster or slower than the equivalent APL expression. We can see that for little objects, the APL solution is faster, but for a large vector, the Assembler routine is much faster. For instance:

      V←10000↑1
      1=+/V
1
      BOOL1 V
1

For such a variable, the Assembler routine is 12 times faster than its APL equivalent. But we have to remember that we should use the APL expression for arguments of more than 32767 elements because the Assembler routine will give an error in that case. It is possible to write an Assembler routine that handles large vectors. Of course, such a routine will be more complex to write, and the execution time will be a bit slower for little objects. Another point is that the APL expression is valid for objects with a rank larger than 1, and for objects containing not only 0’s and 1’s.

We now know how to read an APL variable. We can update a variable as easily. We also have the possibility to create objects directly from Assembler.

Conclusion

This little function shows what we can expect from Assembler: when we want to speed up a very specific operation, the code we have to write is very short, and we can considerably reduce the computing time. Often, we will have to reduce the domain of the operation, and we will avoid unecessary tests. In our example the domain is reduced to vectors of less than 32768 elements containing only 0’s and 1’s.