PC Assembler (II)
Vector 4:3, page 119
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:
In memory, we can find the following bytes, where X represents a byte that does not have a useful meaning for us:
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:
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:
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:
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:
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:
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.