Overview
ObjAsm is the classic Acorn ARM assembler language used by riscos-objasm. It combines ARM instructions, assembler directives, symbolic expressions, conditional assembly, and macro expansion in one source format. This appendix summarises the language structure relevant to normal RISC OS development.
Language reference
Source line structure
The general form of a source line is a label, followed by an instruction or directive, followed by an optional semicolon comment. If a line has no label, it must begin with a space or tab. Empty lines are valid.
Instruction mnemonics and register names may be written in upper or lower case, but not mixed case. Directives are written in upper case. Source lines may be up to 255 characters long, and a trailing backslash continues a line on to the next source line as whitespace.
Comments begin with a semicolon and continue to the end of the logical source line. Strings are written in double quotes. Backslash escapes may be enabled or disabled by assembler options, so portable source should avoid depending on escape processing unless the build rules control it explicitly.
Instruction statements
Instruction statements follow normal ARM assembler style: a mnemonic, optional condition suffix, optional flag-setting suffix, and an operand field separated by commas. Immediate constants use a leading #, while memory references use square brackets around the base register and offset expression.
The source language accepts the usual ARM register names such as R0 to R15, together with APCS (ARM Procedure Call Standard; see Glossary) aliases where they have been predefined by the selected -APCS mode. For example, LR, PC, SP, and, where appropriate, static-base names may be used directly in expressions and addressing modes.
The general shape is:
label mnemonic{cond}{S} operand[,operand]... ; comment
Examples of common operand forms are:
| Form | Meaning |
|---|---|
| MOV R0,#0 | Move an immediate constant into a register. |
| ADD R1,R1,#4 | Arithmetic on register and immediate operands. |
| LDR R0,[R1,#8] | Load from an address formed from a base register and offset. |
| STR R2,[R3],#4 | Store with post-indexed writeback. |
| BNE label | Branch using a condition suffix appended to the mnemonic. |
Areas and program layout
ObjAsm source is normally organised into named AREA sections, which are the indivisible code or data blocks presented later to the linker. Conventional sources define one code area and one writable data area, with an optional static-base area for relocatable address constants in reentrant code.
The syntax is:
AREA name[,attr][,attr]...
Important attributes include:
| Attribute | Meaning |
|---|---|
| REL | Relocatable area. This is the default. |
| ABS | Absolute area rooted at a fixed address. |
| PIC | Position-independent code. |
| CODE | Contains machine instructions. |
| DATA | Contains data rather than instructions. |
| READONLY | Area is not intended to be written to. |
| NOINIT | Zero-initialised data area containing only space reservation. |
| REENTRANT | Marks a code area as reentrant. |
| BASED R n | Static-base data area whose labels become register-relative expressions, conventionally based on R9. |
| ALIGN=expression | Force the area start to a power-of-two byte boundary. |
The ORG directive can set a fixed origin and imply an absolute area, but it is normally only appropriate for special single-area programs that must address fixed hardware locations.
Symbols and labels
Numbers, logical values, string values, and addresses may all be represented by symbols. Symbol names start with a letter, are case-sensitive, and may then contain digits and underscores. If a wider character range is needed, a symbol may be enclosed in bars, such as |C$$code|.
Symbols representing values are declared with GBL or LCL and assigned with SETA, SETL, or SETS. Labels are a special case of symbol defined by position at the start of a line.
ObjAsm also supports local labels, which begin with a number in the range 0-99 and work with the ROUT directive. A local label may be defined more than once, with each definition starting a new active range at the most recent ROUT. An unqualified reference to a local label resolves to its nearest definition in either direction, but a reference may instead be qualified to force a search direction: %Fnn for the nearest following (forward) definition, and %Bnn for the nearest preceding (backward) definition.
Storage definition and reservation
Initialised storage is usually described with:
| Directive | Meaning |
|---|---|
| DCB | Define one or more bytes. String expressions may appear in the expression list. |
| DCW | Define one or more 16-bit halfwords. |
| DCD | Define one or more 32-bit words, including program-relative and external expressions. |
| % | Reserve and zero a number of bytes. |
| DCFS | Define single-precision floating-point constants. |
| DCFD | Define double-precision floating-point constants. |
ObjAsm strings do not add an implicit trailing NUL byte, so a C-style string must be written explicitly, for example:
C_string DCB "C_string",0
Storage maps and register-relative layouts
The ^ and # directives describe the layout of storage maps without directly emitting code or data. The ^ directive sets the storage-map origin and the location counter @, and # reserves space while assigning labels at the current @ value.
With a base register on ^, subsequent # labels become register-relative expressions which can later be used directly in load and store instructions.
Source inclusion and link-time symbols
Source inclusion is handled by GET and INCLUDE, which are synonyms. Once the included file is assembled, the assembler continues at the following line in the including source.
Link-time symbol exchange is handled by:
| Directive | Meaning |
|---|---|
| IMPORT | Declare a symbol resolved later from another object file. It may be marked WEAK or FPREGARGS (the routine passes floating-point arguments in floating-point registers rather than integer registers; see the Glossary). |
| EXPORT | Make a symbol visible to other object files. It may carry FPREGARGS, DATA, or LEAF attributes. |
| KEEP | Retain local symbols in the object-file symbol table, either globally or for one named symbol. |
Assembly organisation
Important organisational directives include:
| Directive | Meaning |
|---|---|
| END | Terminate assembly of the current source file. It is an error to omit it from the top-level file. |
| ORG | Set the program origin, normally only for fixed-address code. |
| LTORG | Emit the current literal pool at this point. A default literal-pool emission happens at each top-level END. |
| ASSERT | Emit a diagnostic during the second pass if a logical expression is false. |
| ! | Emit custom warnings or fatal diagnostics depending on the value of its controlling arithmetic expression. |
Expressions and operators
ObjAsm expressions combine simple values, unary operators, binary operators, and brackets, with a fixed precedence order. Unary operators bind most tightly, and binary operators of equal precedence evaluate from left to right.
Simple values may be arithmetic, logical, string, or address expressions. Address expressions include labels, area-relative values, register-relative values created through BASED areas or storage maps, and program-counter-relative literals produced for link-time relocation.
Useful unary operators include:
| Operator | Meaning |
|---|---|
| ? | Number of bytes generated by the line defining a label. |
| :BASE: and :INDEX: | Extract the base register and offset of a register-relative or PC-relative expression. |
| :LEN: | Length of a string. |
| :CHR: | ASCII string form of a numeric value. |
| :STR: | Hexadecimal string form of a numeric value, or T/F for a logical expression. |
| :NOT: and :LNOT: | Bitwise and logical complement. |
| :DEF: | True if a symbol is defined. |
Binary operators cover arithmetic, bitwise logic, string slicing and concatenation, shifts and rotates, comparisons, and logical combination. The assembler provides multiplicative operators such as *, /, and :MOD:; string operators such as :LEFT:, :RIGHT:, and :CC:; shift operators such as :SHL:, :SHR:, :ROL:, and :ROR:; and the usual bitwise and arithmetic operators such as :AND:, :OR:, :EOR:, +, and -.
Conditional assembly
ObjAsm supports conditional and repetitive source expansion. The core conditional form is marked by [ to begin, | for the else branch, and ] to end, with IF, ELSE, and ENDIF as equivalent word forms.
Conditional blocks may be nested. Skipped lines are omitted from listings unless the assembler is switched out of terse mode with -NoTerse.
The basic pattern is:
[ logical_expression
... first block ...
|
... second block ...
]
Condition expressions use the same logical and comparison operators as other ObjAsm expressions, so symbols set by SETL or SETA are commonly used to select architecture- or feature-specific fragments without editing the source body.
Macros
Macros allow one source-line form to expand into multiple instructions and directives at assembly time. A macro definition begins with MACRO, followed by a prototype statement naming the macro and its parameters, and ends with MEND.
Parameters are strings substituted before syntax analysis. A single optional label parameter may appear in the label field of the prototype, and any number of normal parameters may appear in the operand field. Parameter names begin with $.
The basic pattern is:
MACRO
$label TestAndBranch $dest,$reg,$cc
$label CMP $reg,#0
B$cc $dest
MEND
Subset supported by objasm2gas
The earlier sections describe the general ObjAsm language. The objasm2gas converter and its wrapper forms implement a substantial working subset of that language so that ObjAsm-like source can be translated into GNU assembler syntax for the 64-bit toolchain.
The current upstream README and local converter source confirm support for:
- comments, labels, numeric local labels, and symbol names containing special characters;
- functions and routine markers such as PROC, FUNCTION, ENDP, ENDFUNC, and ROUT;
- section declarations through AREA, including the common RISC OS area naming patterns such as C$$code and C$$data;
- data-definition directives including DCB, DCW, DCD, DCQ, DCFS, and DCFD, together with simple space reservation through SPACE and %;
- symbol-definition directives such as GBLA, GBLL, GBLS, LCLA, LCLL, LCLS, SETA, SETL, SETS, RN, QN, and DN;
- conditional assembly using IF, ELSE, ENDIF and the symbolic forms [, |, and ];
- macro definition and expansion, including macro-local variables and optional label parameters;
- storage-map style field mappings created with ^ and #;
- inline processing or GNU .include emission for GET and INCLUDE;
- RISC OS-style filename lookup and directory-variable forms such as Library:Thing.s and <Export$Dir>;
- numeric literals in binary, octal, hexadecimal, decimal, floating-point, and character-literal forms, together with register-list helper extensions such as :REGLISTLEFT:, :REGLISTRIGHT:, :REGLISTSKIP:, and :REGLISTTRIM:.
Where possible the converter evaluates ObjAsm expressions itself, rewrites the result into GNU syntax, and preserves a source-line map for later diagnostics. That is the mechanism used by the 64-bit wrapper family when it accepts ObjAsm-like source.
Objasm2gas limitations and unsupported cases
The converter is intentionally pragmatic rather than complete. Important cases that remain unsupported or only partially supported are:
- WHILE loops are unsupported. The converter can only map them mechanically to GNU .rept/.endr, so treat loop-heavy source as requiring manual review.
- ELSEIF is rejected outright when objasm2gas is run with --simple-conditions. In the default interpreted-conditional mode, more than one true ELSEIF branch may be executed, so review those translations carefully.
- AREA attributes are only partially modelled. Unrecognised attributes are ignored with a warning, so unusual area metadata may not survive translation faithfully.
- The extended SPACE form with fill value and fill-size arguments is not supported. The converter only implements simple size reservation there.
- The expression-evaluation path used for converted conditionals and preprocessed values is incomplete. Unsupported or partial handling includes ?symbol, :BASE: except for mapping state, :INDEX: except for mapping state, :LNOT:, :RCONST:, :CC_ENCODING:, :REVERSE_CC:, and some rotate-style operators.
- The upstream README cautions that uses of . or {PC} may still be unreliable in some circumstances, so PC-relative expressions should be kept as simple as possible.
- The converter assumes the input is already valid ObjAsm syntax. It is not a recovery parser for malformed source, and warnings in non-strict mode mean only that the tool attempted a best-effort mapping, not that the result is guaranteed correct.
For routine 32-bit development, the classic riscos-objasm assembler remains the authoritative implementation of the full source language. The converter-backed path is primarily for the GNU-based 64-bit toolchain.
Diagnosing translation and assembly failures
Neither tool described in this appendix is silent about failure, but they report it differently. riscos-objasm prints a one-line error referencing the source line and file, followed by the offending source line itself, and exits with a non-zero status. Captured live from a source file containing an invalid shift specifier:
riscos-objasm -from s.bad -to o.bad
Error: Unknown shift name at line 3 in file s/bad
3 00000000 MOV r0, r1, r2, r3
Assembly terminated:
1 Error, 0 Warnings
objasm2gas defines its exit codes explicitly; they can be listed with objasm2gas --return-code:
| Exit code | Meaning |
|---|---|
| 0 | No problems occurred. |
| 1 | Invalid or conflicting command-line arguments. |
| 2 | File I/O error. |
| 3 | Unsupported conversion. |
| 4 | Syntax or processing error. |
| 255 | Generic error code. |
A file I/O failure (exit code 2), captured live by pointing the output at a non-existent directory:
objasm2gas -o /tmp/nofile/out.s bad2.s
ERROR: objasm2gas: Cannot create output '/tmp/nofile/out.s': No such file or directory
Because objasm2gas assumes its input is already valid ObjAsm syntax (see the limitations above), a successful exit code from it confirms only that the recognised directives were translated; it does not confirm that the translated GNU-syntax source will assemble. Always follow a translation with the GNU assembler itself (or --gas to invoke it directly) to confirm the result actually assembles before trusting it.
Worked example
Minimal area structure
This shows the shape of a simple code area with one exported entry point.
AREA |Example$$Code|, CODE, READONLY
EXPORT Example_Entry
Example_Entry
MOV R0, #0
MOV PC, LR
END