Showing changes from revision #6 to #7:
Added | Removed | Changed
When your program wants to use a constant value, such as the &58454241 “ABEX” constant from the hello world example, it can be a pain to have to manually declare that constant using DCD and use a label to reference it. Luckily ObjAsm has a range of functionality available which helps to make using constants easier
MOV R0,#-123
Unlike BASIC, ObjAsm handles “MOV immediate” as a pseudo-instruction. If the immediate constant is outside the range of values directly supported by MOV, ObjAsm will invert the value and try generating a MVN instruction instead. If both of these fail and an ARMv6T2 or later CPU has been selected, ObjAsm will also try using MOVW.
LDR R1,=&58454241 ; "ABEX"
LDR Rd,=
is a pseudo-instruction which allows arbitrary constants to be loaded into registers. When faced with such an instruction, ObjAsm will automatically pick the “best” single ARM instruction which can be used to load the required value. Most of the time this will be MOV, MVN or MOVW, as described above.
For situations where the constant can’t be represented using a single data-processing instruction, ObjAsm will reserve space in the nearest “literal pool” for the value and will generate a PC-relative LDR. If there are multiple uses of the same constant then ObjAsm will try and re-use existing literal pool values wherever possible.
MOV32 R1,#&58454241 ; "ABEX"
MOV32 is another pseudo-instruction which can be used with constant values. It assembles to a MOVW and MOVT pair. ARM say that in most situations this gives better performance than using a literal pool, but the downsides are that the pseudo-instruction is only available with ARMv6T2 and later, and it will always assemble to two instructions (even if a single instruction would be sufficient).
Sadly there is currently no “best of both” available out of the box, although possibly one could be constructed using macros, at least for situations where the constant is a true constant and not a reference to a label (discussion)
Although not directly related to constants and literal pools, it’s worth mentioning ADRL here as well, since it’s another useful pseudo-instruction. It generates a two-instruction ADR, offering a significant range increase over the standard one-instruction form that’s supported by BASIC (although there are several add-ons available which introduce ADRL to BASIC).
For LDR Rd,=
to work in all situations, there must be a literal pool located within 4KB of the instruction (since LDR with an immediate offset is limited to an offset of +/-4KB from the base register). Use the LTORG
directive to place the literal pool:
LTORG
Since the pool will expand to an arbitrary size and will be filled with data, you should obviously avoid placing it within the flow of execution of a function.
You already know how to use *
to define symbolic constants. Although this can be used to define the layout of structures and workspace, there’s an alternative method available, known as “storage maps”, which is much more flexible and powerful.
For example, a simple linked list of key-value pairs could be described using *
as follows:
KV_Next * 0 ; Pointer to next node in list KV_Key * 4 ; One byte to act as a key KV_Value * 5 ; One byte to act as a value KV_Size * 6 ; Size of a KV list entry
The same thing, rewritten to use the “storage map” functionality would look like this:
^ 0 KV_Next # 4 ; Pointer to next node in list KV_Key # 1 ; One byte to act as a key KV_Value # 1 ; One byte to act as a value KV_Size # 0 ; Size of a KV list entry
Both describe the same structure, but different methods of specifying the method offsets the are structure used. is defined is different. With*
you must manually keep track of the offsets of items relative to the base of the structure. With #
you just specify the sizes of the items and ObjAsm will keep track of the offsets for you. When you’re working with large structures this makes it much easier to add, remove or change individual elements.
The ‘0’ after the ‘^’ specifies the offset at which the storage map starts. Usually you’d just leave this as zero.
One powerful feature of the storage map functionality is the ability to specify a base register. For example, most modules allocate a block of memory from the RMA to use as their workspace, and use R12 to point to that block. Defining the layout of the module workspace via a storage map with a base register makes it quick and easy to reference the workspace within code.
^ 0, R12 StartTime # 4 SomeBuffer # 256 WorkSize * :INDEX: @ Module_Init ROUT MOV R5,LR ; Claim workspace LDR R3,=WorkSize MOV R0,#6 SWI XOS_Module MOVVS PC,R5 ; Store workspace pointer in module private word STR R2,[R12] ; Transfer workspace pointer to R12 so references will work MOV R12,R2 ; Zero workspace MOV R0,#0 10 SUBS R3,R3,#4 STR R0,[R12,R3] BNE %BT10 ; Initialise workspace with stuff for your module SWI XOS_ReadMonotonicTime STR R0,StartTime ; Exit MOV PC,R5 ; SWI 0: Get module start time ; SWI 1: Return pointer to internal buffer ; etc. Module_SWI ROUT LDR R12,[R12] CMP R11,#0 LDREQ R0,StartTime MOVEQ PC,LR CMP R11,#1 ADREQ R0,SomeBuffer MOVEQ PC,LR ADR R0,BadSWIError MSR CPSR_f, #1<<28 ; Set V MOV PC,LR
The above code highlights a few ways storage maps with base registers can be used.
WorkSize
constant) you need to use the special :INDEX:
operator. This strips the base register from its operand. In the example above, ‘@’ is used; this is a special variable which tracks the current location within a storage map definition. The earlier example of KV_Size could also be rewritten to use ‘@’ as follows:... KV_Value # 1 ; One byte to act as a value KV_Size * @ ; Size of a KV list entry
Of course one downside of storage maps with base registers is that it becomes awkward to use them in situations where you have different registers which point to the same type of data structure. Every access which isn’t via the usual base register will have to use :INDEX:
to strip the reference, e.g.
LDR R0,[R1,#:INDEX:StartTime]
You can add padding to your storage map definition simply by adding a ‘#’ line without specifying a name. For example, we could re-arrange the key-value list example so that the KV_Next pointer comes last:
^ 0 KV_Key # 1 ; One byte to act as a key KV_Value # 1 ; One byte to act as a value # 2 ; Pad to word alignment KV_Next # 4 ; Pointer to next node in list KV_Size * @ ; Size of a KV list entry