|
APCS(ARM Procedure Call Standard) |
|
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:
hoc 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 preprocessor, 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 objasm, though other assemblers (such
as Nick Roberts' ASM) may define them for you.
To define a register name, 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 15
That example shows us two important things:
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
END
When 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.
I have tried the program on several popular assemblers, and have noted the versions that I have used...
AAsm 2.00 (Acorn) v2.00
This is a simple assembler that doesn't support areas, and it outputs an Absolute file (there is
no linker stage).
Removing the AREA and ENTRY lines will allow the file to compile correctly.
The output is 60 bytes long, as there is no AOF header.
ARMmaker 0.52 (Steven Haslam)
I cannot get this to output AOF files. I define ENTRY start and
AREA main, CODE, READONLY at the start of the program, and
ARMmaker replies "Entry point "start" not defined".
I put this down to my unfamiliarity with the program. Asking ARMmaker to output an absolute file
worked, creating a 60 byte program (identical to AAsm's output).
The code used for ARMmaker was:
r0 BINDREG R0
OUTPUT ABSOLUTE
start
ADR r0, title
SWI "OS_Write0"
SWI "OS_GetEnv"
SWI "OS_Write0"
SWI "OS_NewLine"
SWI "OS_Exit"
title
DCS "This program was called with:\n\r "
DCB 0
ALIGN
Like Nick Roberts' ASM, this assembler can convert SWI names for you.
This guide was written in France. When I return to England, I shall contact the author of ARMmaker to clarify how AOF files are created, and will update this when I find out.
AS 1.21 (Niklas Röjemo)
AS was quite happy with the original objasm input.
As a bonus, AS can also cope with SWI name conversions.
AS is not a 'better' version of objasm as it lacks the macro facilities and the more exotic
features of objasm. However, on the plus side it is supplied with source so you can add your own
custom bits.
ASM 4.10 (Nick Roberts)
One of the more complex (and powerful) of the free assemblers, ASM supports IF...THEN, macros,
different processors, 32 bit code, high-level structures, includes, labels, maths in instructions
(such as DCFE 12345.6 + (1 << 12)).
All in all, this may rate as my favourite assembler. I originally wrote it as my favourite non-Acorn assembler, but ASM now leaves objasm standing... Then again, it leaves the other assemblers standing too!
ASM failed to recognise the RN directive. This isn't a big issue, as the standard (r#) and APCS
names are understood by the assembler.
ASM requires that labels are suffixed with a colon and, like ARMmaker, it wasn't happy with the
string syntax so the more traditional EQUS/EQUB has been used.
END was not required, and as you can see ASM is capable of converting SWI names on the fly.
I am describing the differences in ASM so verbosely not because I want to pick on ASM, but
because I feel that Nick's minor changes (such as the colon) and the SWI conversion all make for
much tidier looking code. I also like the EQUx directives, I use them in BASIC and feel happier
with them than with DCx or stuff like '='.
The code used for ASM was:
AREA main, CODE, READONLY
ENTRY start
start:
ADR r0, title
SWI "OS_Write0"
SWI "OS_GetEnv"
SWI "OS_Write0"
SWI "OS_NewLine"
SWI "OS_Exit"
title:
EQUS "This program was called with:\n\r "
EQUB 0
ALIGN
STOP PRESS!
objasm 2.00 (Acorn)
TLA 0.1f (G.F.A.@ Lancaster)
But don't be discouraged. Assembler has its uses. Here are some examples:
ASM is now up to version 4.10. It supports instructions for all processors ARM2 to StrongARM,
with target settings so you can't compile invalid code for a given processor. It also features
built-in assembly-time mathematical functions (sin, cos, tan, acs, etc etc), floating point
code, NOPs, macros, full conditional assembly, structures (!!!), limited-scope labels, pragmas,
and redefinable constants.
ASM now accepts code in 's' or 'a', or anywhere else you wish to put your code.
As always, a descriptive manual is supplied in Impression or text format.
Download ASM
This was used as the benchmark, so was obviously happy with the input!
A capable assembler, this was supplied with the Acorn Desktop assembler package. A later and more
capable (ie, later processors/facilities) version is now provided with Acorn's C++ development
suite.
TLA is a very peculiar assembler. It appears to have a central core which is machine independent
along with various opcodes for the ARM processor.
It is a peculiar assembler (in my opinion), and I wasn't able to get an ADR style instruction
assembled. To be precise, the OS_Write0 example (in the documentation) crashed with the same
error (branch through zero at <somewhere in ROM>).
But, for interests sake, I have recoded the program to work with TLA, though I would recommend
an a different assembler - something that is closer to the DDE style.
The code used for TLA was:
.ENTRY start
.CODEAREA
.PROC main
start
SWI OS_WriteS
.ASCII "This program was called with:"
SWI OS_NewLine
SWI OS_WriteS
.ASCII " "
SWI OS_GetEnv
SWI OS_Write0
SWI OS_NewLine
SWI OS_Exit
.END
The examples
The examples have been written for objasm.
More...
A full explanation of APCS and assemblers is out of the scope of this guide. Assuming that you
are familiar with assembler, the instructions for ASM and ARMmaker are both
interesting reads. These two documents should provide you with sufficient information to obtain
good results from your assembler, whichever you choose, and help you write productive code.
However, for complete instructions (should your require them), you would need to read the PRMs
and the Acorn Assembler documentation.
And finally...
You will (by the time you've read the details for example 6), realise that I am overstressing
the following point:
Don't recode everything in assembler because you can.
Modern compilers aren't stupid. The Norcroft v4.00 compiler, apparently, doesn't generate
totally optimised code [source: lots of arguments on comp.sys.acorn.programmer over the years]
but it does perform some optimisations. And, instruction for instruction, a compiler can
probabaly out-optimise many of you. I, for one, wouldn't want to take on a compiler against my
code.
I'm sure you can think of more examples that might benefit from being coded in assembler.
Please be sure to read 'Don't be over zealous'.
DeskLib 2.30 took a simple WIMP command - 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)
An interesting challenge would be to ask your compiler to generate a source listing
rather than a compiled object file (for Norcroft C, use the -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!).
This is also a good way to kickstart your own assembler code. If it is slow(ish), and
doesn't call too many library functions, that you can knock up a 'working model' in C or
Pascal (both Norcroft compilers will output ARM assembly code with the -S flag) and then
you have a functional base to improve upon.
Return to assembler index