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:
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 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 15That 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 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.
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 ALIGNLike 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 3.1 (Nick Roberts)
STOP PRESS - click for latest info on ASM
Trying to assemble the code was met with the error "Internal error in translate_name : Bad file type character".
A rather unfriendly error, what it means is that ASM seems to prefer its code to be in a
subdirectory called 'a
' ('a' for assembler, more logical than 's' I guess...).
To be fair to Nick, this is one of the first things mentioned in the documentation - so sometimes
it pays to RTFM. However, 's' is the convention, and that message could have been more
helpful...
[editors note: fixed, see note below]
One of the more complex of the free assemblers, ASM supports IF...THEN, macros, different processors. All in all, this may rate as my favourite non-Acorn assembler. But note that, unlike Steven Haslam's ARMmaker, ASM requires a linker. However, linkers are available - such as DrLink.
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!
ASM is now up to version 4.09. 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.
I've not had time to review or use this version of ASM, but I get the feeling that it has left
all of the other assemblers behind.
Download ASM
objasm 2.00 (Acorn)
This was used as the benchmark, so was obviously happy with the input.
TLA 0.1f (G.F.A.@ Lancaster)
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
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
-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!).