Writing an APCS program |
The APCS, or ARM Procedure Call Standard, provides a mechanism for writing tightly defined routines in assembler. This may seem possibly wasteful when you consider the case of writing your own project entirely in assembler. However, modern applications tend to be of a hybrid fashion - the base written in a high level language, with speed-critical parts written in assembler. Then, APCS comes into its own.
In a DDE application, several directories are used:
h
o
c
is used for C code, it stands to reason that s
is used for
assembler.s
'.
APCS defines the registers using different names to our usual R0 to R14. With the power of the assembler pre-processor, you can define R0 etc, but it is just as well to learn the APCS names in case you are modifying code written by others.
Register names | ||
Reg # |
APCS |
Meaning |
R0 |
a1 |
Working registers |
R1 |
a2 |
" |
R2 |
a3 |
" |
R3 |
a4 |
" |
R4 |
v1 |
Must be preserved |
R5 |
v2 |
" |
R6 |
v3 |
" |
R7 |
v4 |
" |
R8 |
v5 |
" |
R9 |
v6 |
" |
R10 |
sl |
Stack Limit |
R11 |
fp |
Frame Pointer |
R12 |
ip |
|
R13 |
sp |
Stack Pointer |
R14 |
lr |
Link Register |
R15 |
pc |
Program Counter |
These names are not defined by standard in Acorn's older objasm, though other assemblers
(such as Nick Roberts' ASM, and the later (32 bit compatible) objasm) should define them
for you.
To define a register name in objasm, you use the RN
directive, at the very
start of your program:
a1 RN 0 a2 RN 1 a3 RN 2 ...etc... r13 RN 13 sp RN 13 r14 RN 14 lr RN r14 pc RN 15That example shows us two important things:
You can set up a header file, if you don't want to do all of this in every module you write. Then, for objasm, you simply:
GET h.regnamesFor ASM, you would use the INCLUDE directive. Other assemblers may vary.
We define this with:
AREA |main|, CODE, READONLYThis sets up an area called "main". The area name must be enclosed in vertical bars, and it may be any valid identifier.
If we were linking our code with C, then we would call the area "|C$$code|". This is described in mode detail in example 6. You cannot call a function "main" when interworking with C, as by convention the initial function of a C program is main(). Likewise, you cannot call your function the same as an existing one.
ENTRYto tell the assembler that this is where your program wishes to be entered. You can only have one entry point per program.
When interworking with a high level language, you do not have an entry point. Instead, you have
a collection of routines that are exported. Refer to example 6 for more
information.
Please note, there is quite a lot of stuff that should be set up for a stand-alone assembler
program, such as reading command line parameters and stack management. It gets even hairier if
you wish to write a multitasking application entirely in assembler. Therefore, it is recommended
that you write the basis of your program in C, and use assembler for the parts that require the
speed benefit. However, example 5 will describe a simple utility written
entirely in assembler.
Now is the time to explain why the previous examples were indented eight spaces, except for the register definitions.
In assembler code, there is a very simple syntax. Things on the left are counted as identifiers,
while things that are indented are either instructions or directives.
Any line beginning with a semicolon is a comment, and a semicolon in a line of code means that
the rest of the line is a comment.
You do not start labels with a period.
Unlike BASIC, a colon does not start a new instruction. You can only have one instruction
per line.
For example:
r0 RN 0 AREA |main|, CODE, READONLY ENTRY ADR r0, title SWI &02 ; OS_Write0 SWI &10 ; OS_GetEnv SWI &02 ; OS_Write0 SWI &03 ; OS_NewLine SWI &11 ; OS_Exit title = "This program was called with:", 10, 13, " ", 0 ALIGN ENDWhen run, the program outputs a short title, followed by the command line that started the program. For example:
TaskWindow Server v0.01 *cat Dir. IDEFS::Willow.$.Coding.Projects.Assembler.apcstest Option 00 (Off) CSD IDEFS::Willow.$.Coding.Projects.Assembler.apcstest Lib. IDEFS::Buffy.$.library URD IDEFS::Stephanie.$ o D/ s D/ test WR/ *test -this -is a -test This program was called with: test -this -is a -test * |
SWI
"OS_Exit"
will read the SWI name and calculate the SWI number for you; and ARMmaker will
go as far as to allow you to specify output types (ABSOLUTE, MODULE, AOF, etc) which is very
useful if you don't have a linker.
You may also like to refer to my opinions on various assemblers.
But don't be discouraged. Assembler has its uses. Here are some examples:
Wimp_ProcessKey
and implemented it
in only four lines of assembler:MOV ip, lr SWI SWI_Wimp_ProcessKey + XOS_Bit MOVVC a1, #0 MOVS pc, ip ; (not 32-bit friendly)
-S
flag). Load
this file into your favourite editor, and read it. Can it be optimised? Is it overly
peculiar (warning: compiler code can sometimes defy logic - trust it, it (usually!) knows
what it is doing!).Please be sure to read 'Don't be over zealous'.