ASSEMBLER WORKSHOP

David Spencer and Lee Calcraft kick off a new series.

Assembler Workshop is a new occasional series which will be devoted entirely to programming in ARM assembler. We will aim to cover a wide variety of material for both the expert and the relative beginner. This month we kick off with a number of hints and tips.

SIMPLE DEBUGGING

It is probably true to say that when writing machine code routines, at least as much time will be spent in debugging the final code as in structuring and writing it in the first place - so some notes on debugging may not come amiss.

Firstly it is often much more effective to debug your code as you go along, since once you have put a number of interrelated segments together, it will usually be much harder to isolate a bug. Often, very simple tests can be used. For example, a short routine is given below which displays in both decimal and hex the contents of register R0. The routine has the merit of preserving all registers, so that it can be used invisibly.

To check the contents of R12 (say), just use the two instructions:
MOV R0,R12:BL debug

Sometimes you just need to know how far through a routine your code has reached before it crashes (especially if the crash is a fatal error). This is easily achieved by inserting instructions such as:
SWI 256+7
to generate a beep, or
SWI 256+ASC("*")
to place an asterisk on the screen.

For more serious and intensive debugging, you can't beat the Single-Stepper published in this and last month's RISC User.

10 REM >Debug3
20 REM Simple debugging aid
30 REM by Lee Calcraft
40 :
50 DIM code% &100
60 PROCassemble
70 CALL test
80 END
90 :
100 DEFPROCassemble
110 FOR pass=0 TO 2 STEP 2
120 P%=code%
130 [OPT pass
140 .test
150 STMFD R13!,{R14}
160 MOV R0,#&10000
170 BL debug
180 LDMFD R13!,{PC}
190 \----------------
200 \ Print conts of R0 as dec & hex
210 .debug
220 STMFD R13!,{R0-R3,R14}
230 ADR R1,debugspace
240 MOV R3,R0
250 MOV R2,#&20
260 SWI "OS_ConvertInteger4"
270 MOV R0,#32
280 STRB R0,[R1],#1
290 STRB R0,[R1],#1
300 MOV R0,#ASC("&")
310 STRB R0,[R1],#1
320 MOV R0,R3
330 SWI "OS_ConvertHex8"
340 ADR R0,debugspace
350 SWI "OS_Write0"
360 SWI "OS_NewLine"
370 LDMFD R13!,{R0-R3,R15}
380 .debugspace
390 EQUS STRING$(&20,CHR$0)
400 \-----------------
410 ]NEXT
420 ENDPROC

ERROR FREE SWI CALLS

A very useful feature of RISC OS (and Arthur) is that all SWI calls can be made to return to the caller if an error occurs, rather than 'crashing out'. To do this, you simply set bit 17 of the SWI number (add &20000), or if the name is used prefix it with an 'X' (for example "XOS_Byte" instead of "OS_Byte". The use of 'error free' SWIs is very useful for error handling, and is essential when writing utilities and modules, as the error handler must not be called in these circumstances.

In fact, from the point of view of the routine implementing the SWI, the situation is reversed. All SWI calls return with the overflow set if they wish to generate an error, and clear otherwise. However if the non-X form of the SWI is used, RISC OS intercepts the error result, and generates an actual error. In the case of an error occuring, R0 will point to the error number followed by the error string. We will return to this is a future Assembler Workshop when we look at writing modules.

OVERFLOW ANTICS

As just stated, the overflow flag is used by RISC OS as an error indicator with certain calls. Normally, RISC OS ensures that when a routine is called the overflow flag is clear (the exception being certain vector handlers), and therefore the flag is ready for an error-free exit. Unfortunately, many arithmetic instructions potentially alter the value of the overflow flag, accidentally signalling an error. The easiest way to prevent this is to always restore the flags on exit from a routine whenever the routine doesn't return an error flag. This is done using:
MOVS PC,R14
when the return link has not been saved, or
LDMFD R13!,{PC}^
when the link has been stacked. Note the addition of the 'S' or '^' to restore the flags.

WATCH OUT DCW

The DCW instruction is present in both Basic's assembler (where EQUW is a synonym) and in AASM (Acorn's standalone assembler). Its purpose is to store a sixteen bit value in two successive bytes of memory, low byte first (so-called little endien form). However, there is a subtle difference between Basic and AASM. Basic will place the value in the location pointed to by the current instruction pointer, and the following location. AASM on the other hand will align the instruction pointer to a sixteen-bit boundary first. To mimic the Basic assembler's behaviour under AASM, the following macro can be used:
MACRO
NDCW$word
=$word :MOD: 256
=$word / 256
MEND

This defines the instruction NDCW (Non-aligned DCW) to mimic Basic's DCW. In other words, the argument is stored in two consecutive locations without any pre-alignment.

SIGNED ARITHMETIC

The ARM's arithmetic instructions (ADD, SUB, ADC etc.) can all work with both unsigned and signed numbers. Unsigned values stored in a single 32-bit word are in the range 0 to 232-1, and are stored as pure binary. Signed numbers on the other hand are in the range -231 to 231-1. Positive numbers, including zero, are stored in pure binary, while negative numbers are stored in so-called two's complement form. To find the value stored for a negative number, take its positive equivalent, invert all the bits (ones become zeroes and vice versa), and add one to the result. As an example, consider representing -6 in a 4-bit number.
Plus 6 would be stored as0110
Inverting the bits gives1001
Adding one leaves1010

Adding one is necessary to remove the anomaly of +0 and -0 being different, which causes problems with arithmetic. This also explains why the range of possible values is not symmetrical around zero.

A bit of experimentation on paper should convince you that the operations of addition and subtraction are identical whether the arguments are signed or unsigned, and the result will be in the correct form. Of course, you can't have one argument signed, and the other unsigned. It should also be immediately obvious that all positive numbers have their top bit clear, and negative numbers the top bit set. Furthermore, negating a number is done in the same way as creating a negative number from its positive equivalent. If the ARM register R0 holds a signed value, it can be negated using:
MVN R0,R0ADD R0,R0,#1
Similarly, to obtain the absolute value of a number in R0 use:
TST R0,#1<<31MVNNE R0,R0ADDNE R0,R0,#1

One difference with signed arithmetic is that the carry flag no longer gives an indication of an overflow condition. For example, adding two negative numbers will always produce a carry, although this does not necessarily mean an overflow has occurred. To overcome this, the ARM has a separate Overflow flag which is defined to be the Exclusive-OR of the actual carry, and the carry out of bit 30. This flag then gives a true overflow indication, being set if the signed result of a calculation is too big.

The ARM multiply (MUL) and multiply and accumulate (MLA) instructions will also work with both signed and unsigned arguments. However, this is only the case when multiplying two 16-bit arguments to give a 32-bit result, and it should be noted that neither MUL or MLA set the overflow or carry flags to a useful value.

The operation of dividing a number by two by shifting it right can also be applied to signed numbers, but rather than replacing the top bit with a zero, it is necessary to replicate the previous top bit. This is exactly what the ASR (Arithmetic Shift Right) shift operation does, while LSR (Logical Shift Right) shifts in zeros. Hence:
MOV R0,R0,ASR #1
will halve the value in R0, while:
MOV R0,R0,LSR #1
will only work correctly for positive signed numbers (or unsigned numbers).

Multiplying a number by two by shifting it left will also work, but care needs to be taken, as if an overflow occurs this will manifest itself as a change of the sign of the number.

A final operation that is sometimes necessary on a signed number is the process of sign extension. This operation is used to make a number longer (in the sense of being stored in more bits). For example, suppose that you had an analogue to digital converter on a podule which returned 8-bit signed numbers, and you wanted to store these as 32-bit values. It is not enough merely to zero the top twenty-four bits (as LDRB does automatically), because the sign will be incorrect. Instead, what is needed is to replicate the top bit of the original number into all of the unused bits. To convert the 8 bits in 32 the following code could be used:
MOV R0,R0,ASL #32-8
MOV R0,R0,ASR #32-8

This works by moving the original eight bits up to the top of the word, and then down again, using the fact that ASR will automatically replicate the sign.

TYPING AASM FILES

When using AASM, all assembled code is saved with a load and execution address rather than a filetype. This can be annoying when assembling a utility or module as the filetype must be set before the code can be tested. However, AASM can be made to stamp a file using the LEADR directive, for example:
LEADR &FFFFFA00
where the filetype is given in the 4th to 6th digits - FFA for a module, FFC for a utility, etc. This works by exploiting the way in which RISC OS stores filetypes and date stamps in the fields used for the load and execution addresses.