LDR pseudoinstruction - c

when I create ARM assembly code from C code with gcc -S, I get a variant of the LDR instruction that I don't know. Specifically, I get the "ldr r3, .L5" instruction where ".L5" is a lable defined by the compiler. It is not clear to me why I don't get the pseudoinstruction "ldr r3, =.L5", which should be the only way to load an arbitrary number in a register.
More in details:
I start from this C code (file name: sum_squares_C.c):
int sum;
int main(){
sum = 0;
for(int i=1; i<=n; i++){
sum = sum + i*i;
}
}
Then on a Raspeberry PI, I compile with "gcc -O0 -S sum_squares_C.c", with compiler version gcc (Raspbian 8.3.0-6+rpi1) 8.3.0.
The output is this ARM code (the instruction "ldr r3, .L5" is in the 7th line after label "main"):
.arch armv6
.eabi_attribute 28, 1
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 1
.eabi_attribute 18, 4
.file "sum_squares_C.c"
.text
.global n
.data
.align 2
.type n, %object
.size n, 4
n:
.word 1
.comm sum,4,4
.text
.align 2
.global main
.arch armv6
.syntax unified
.arm
.fpu vfp
.type main, %function
main:
# args = 0, pretend = 0, frame = 8
# frame_needed = 1, uses_anonymous_args = 0
# link register save eliminated.
str fp, [sp, #-4]!
add fp, sp, #0
sub sp, sp, #12
ldr r3, .L5
mov r2, #0
str r2, [r3]
mov r3, #1
str r3, [fp, #-8]
b .L2
.L3:
ldr r3, [fp, #-8]
ldr r2, [fp, #-8]
mul r2, r2, r3
ldr r3, .L5
ldr r3, [r3]
add r3, r2, r3
ldr r2, .L5
str r3, [r2]
ldr r3, [fp, #-8]
add r3, r3, #1
str r3, [fp, #-8]
.L2:
ldr r3, .L5+4
ldr r3, [r3]
ldr r2, [fp, #-8]
cmp r2, r3
ble .L3
mov r3, #0
mov r0, r3
add sp, fp, #0
# sp needed
ldr fp, [sp], #4
bx lr
.L6:
.align 2
.L5:
.word sum
.word n
.size main, .-main
.ident "GCC: (Raspbian 8.3.0-6+rpi1) 8.3.0"
.section .note.GNU-stack,"",%progbits
It seems to me that gcc uses the instruction "ldr r3, .L5" as equivalent to "ldr r3, =.L5". Is it correct? Where can I find the definition of this instruction syntax? Is it possible to force gcc to not use this instruction, but use "ldr r3, =.L5" (I need this for teaching reasons)?
Thanks!
Francesco

ldr r3, .L5 loads a word from the address .L5 into r3. At the label .L5 there is the address of the variable sum. So this loads the address of sum into r3.
ldr r3, =.L5 loads the address of .L5 into r3. Then the program would need to dereference it again in order to get the address of sum. There is no reason to do this.
When you use ldr r3, =.L5 the assembler stores the address of .L5 somewhere, and then loads from that address. So this:
ldr r3, =.L5
...
.L5:
.word sum
is the same as this:
ldr r3, .address_of_L5
...
.L5:
.word sum
...
.address_of_L5:
.word .L5
As you can see, the compiler has already done this for sum. Instead of writing this assembly:
ldr r3, =sum
the compiler has written:
ldr r3, .L5
...
.L5:
.word sum
which is exactly what the assembler would have done anyway. I don't know why the compiler wants to do this instead of the assembler.
It is not clear to me why I don't get the pseudoinstruction "ldr r3, =.L5", which should be the only way to load an arbitrary number in a register.
Notice this is not the only way to load an arbitrary number into a register. It's not even a real way to load an arbitrary number into a register. It's a pseudoinstruction (as you know): it's not something the CPU can actually do, it's something that the assembler can "compile" for your convenience.

To save typing and assume a risk a person might use:
ldr r3,=sum
ldr r3,[r3]
As pointed out in the other example the assembler will create in machine code the equivalent of what the human could have typed without the =address trick:
ldr r3,address_of_sum (without the =)
ldr r3,[r3]
...
address_of_sum: .word sum
And that first ldr (not pseudo as it translates directly into a known instruction, one to one) is a pc-relative load (assuming it can reach).
Both of these though are assembler specific as assembly language is defined by the assembler not the target.
The =address shortcut is not supported by all arm assemblers and should be used with care, for certain values it does not turn into a word in the pool with a pc relative load.
For questions like this first examine the disassembly, most of the time that will answer your question, even better examine the dissasembly first then in question the assembly. Compiler generated assembly is not as easy to read and follow as a disassembly, especially when linked. It is also easier to learn from optimized code than unoptimized as so much of the code is this stack (or in this case global) variable stuff.
ldr r3,=0x1000
ldr r3,=0x1234
b .
00000000 <.text>:
0: e3a03a01 mov r3, #4096 ; 0x1000
4: e51f3000 ldr r3, [pc, #-0] ; c <.text+0xc>
8: eafffffe b 8 <.text+0x8>
c: 00001234 andeq r1, r0, r4, lsr r2
In one case where it can it generates a mov, where it cant then it allocates from the pool and places the value there then does a pc relative load. Now yes when reading the output this way you need to see/understand/ignore the andeq disassembly that line we are looking at the value 0x00001234 and seeing the instruction generated.
You should not always assume the =address trick will work if you choose to try various tools, it works for gnu now if it can find a pool if it can't then you either need to just do the typing yourself or add a .pool or whatever the other pseudocode that does the same thing is to help the assembler find a place for this value as needed.
I would expect an assembler to always place this (=address) in the pool for an external reference, but it is technically possible for a toolchain to put a placeholder there and let the linker fill it in either with a mov or add a nearby item and place the value there like binutils does with a bl to an external reference.
gas:
ldr r3,=sum
b .
00000000 <.text>:
0: e51f3000 ldr r3, [pc, #-0] ; 8 <.text+0x8>
4: eafffffe b 4 <.text+0x4>
8: 00000000 andeq r0, r0, r0
The linker will fill in the address later as with your compiler output. Now the -0 disassembly is very interesting, almost amusing.

Related

Does arm-none-eabi-gcc produce slower code than Keil uVision

I have a simple blinking led program running on STM32f103C8 (without initialization boilerplate):
void soft_delay(void) {
for (volatile uint32_t i=0; i<2000000; ++i) { }
}
uint32_t iters = 0;
while (1)
{
LL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
soft_delay();
++iters;
}
It was compiled with both Keil uVision v.5 (default compiler) and CLion using arm-none-eabi-gcc compiler.
The surprise is that arm-none-eabi-gcc program runs 50% slower in Release mode (-O2 -flto) and 100% slower in Debug mode.
I suspect 3 reasons:
Keil over-optimization (unlikely, because the code is very simple)
arm-none-eabi-gcc under-optimization due to wrong compiler flags (I use CLion Embedded plugins` CMakeLists.txt)
A bug in the initialization so that chip has lower clock frequency with arm-none-eabi-gcc (to be investigated)
I have not yet dived into the jungles of optimization and disassembling,
I hope that there are many experienced embedded developers who already encountered this issue and have the answer.
UPDATE 1
Playing around with different optimization levels of Keil ArmCC, I see
how it affects the generated code. And it affects drastically, especially execution time. Here are the benchmarks and disassembly of soft_delay() function for each optimization level (RAM and Flash amounts include initialization code).
-O0: RAM: 1032, Flash: 1444, Execution Time (20 iterations): 18.7 sec
soft_delay PROC
PUSH {r3,lr}
MOVS r0,#0
STR r0,[sp,#0]
B |L6.14|
|L6.8|
LDR r0,[sp,#0]
ADDS r0,r0,#1
STR r0,[sp,#0]
|L6.14|
LDR r1,|L6.24|
LDR r0,[sp,#0]
CMP r0,r1
BCC |L6.8|
POP {r3,pc}
ENDP
-O1: RAM: 1032, Flash: 1216, Execution Time (20 iterations): 13.3 sec
soft_delay PROC
PUSH {r3,lr}
MOVS r0,#0
STR r0,[sp,#0]
LDR r0,|L6.24|
B |L6.16|
|L6.10|
LDR r1,[sp,#0]
ADDS r1,r1,#1
STR r1,[sp,#0]
|L6.16|
LDR r1,[sp,#0]
CMP r1,r0
BCC |L6.10|
POP {r3,pc}
ENDP
-O2 -Otime: RAM: 1032, Flash: 1136, Execution Time (20 iterations): 9.8 sec
soft_delay PROC
SUB sp,sp,#4
MOVS r0,#0
STR r0,[sp,#0]
LDR r0,|L4.24|
|L4.8|
LDR r1,[sp,#0]
ADDS r1,r1,#1
STR r1,[sp,#0]
CMP r1,r0
BCC |L4.8|
ADD sp,sp,#4
BX lr
ENDP
-O3: RAM: 1032, Flash: 1176, Execution Time (20 iterations): 9.9 sec
soft_delay PROC
PUSH {r3,lr}
MOVS r0,#0
STR r0,[sp,#0]
LDR r0,|L5.20|
|L5.8|
LDR r1,[sp,#0]
ADDS r1,r1,#1
STR r1,[sp,#0]
CMP r1,r0
BCC |L5.8|
POP {r3,pc}
ENDP
TODO: benchmarking and disassembly for arm-none-eabi-gcc.
This second answer is a demonstration of the kinds of things that would affect the performance results the OP may be seeing and examples of to possibly test for those STM32F103C8 blue pill.
Complete source code:
flash.ld
MEMORY
{
rom : ORIGIN = 0x08000000, LENGTH = 0x1000
ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > rom
.rodata : { *(.rodata*) } > rom
.bss : { *(.bss*) } > ram
}
flash.s
.cpu cortex-m0
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang: b .
.align
.thumb_func
.globl PUT32
PUT32:
str r1,[r0]
bx lr
.thumb_func
.globl GET32
GET32:
ldr r0,[r0]
bx lr
.thumb_func
.globl dummy
dummy:
bx lr
test.s
.cpu cortex-m0
.thumb
.word 0,0,0
.word 0,0,0,0
.thumb_func
.globl TEST
TEST:
bx lr
notmain.c
//PA9 TX
//PA10 RX
void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );
void dummy ( unsigned int );
#define USART1_BASE 0x40013800
#define USART1_SR (USART1_BASE+0x00)
#define USART1_DR (USART1_BASE+0x04)
#define USART1_BRR (USART1_BASE+0x08)
#define USART1_CR1 (USART1_BASE+0x0C)
#define USART1_CR2 (USART1_BASE+0x10)
#define USART1_CR3 (USART1_BASE+0x14)
//#define USART1_GTPR (USART1_BASE+0x18)
#define GPIOA_BASE 0x40010800
#define GPIOA_CRH (GPIOA_BASE+0x04)
#define RCC_BASE 0x40021000
#define RCC_APB2ENR (RCC_BASE+0x18)
#define STK_CSR 0xE000E010
#define STK_RVR 0xE000E014
#define STK_CVR 0xE000E018
#define STK_MASK 0x00FFFFFF
static void uart_init ( void )
{
//assuming 8MHz clock, 115200 8N1
unsigned int ra;
ra=GET32(RCC_APB2ENR);
ra|=1<<2; //GPIOA
ra|=1<<14; //USART1
PUT32(RCC_APB2ENR,ra);
//pa9 TX alternate function output push-pull
//pa10 RX configure as input floating
ra=GET32(GPIOA_CRH);
ra&=~(0xFF0);
ra|=0x490;
PUT32(GPIOA_CRH,ra);
PUT32(USART1_CR1,0x2000);
PUT32(USART1_CR2,0x0000);
PUT32(USART1_CR3,0x0000);
//8000000/16 = 500000
//500000/115200 = 4.34
//4 and 5/16 = 4.3125
//4.3125 * 16 * 115200 = 7948800
PUT32(USART1_BRR,0x0045);
PUT32(USART1_CR1,0x200C);
}
static void uart_putc ( unsigned int c )
{
while(1)
{
if(GET32(USART1_SR)&0x80) break;
}
PUT32(USART1_DR,c);
}
static void hexstrings ( unsigned int d )
{
//unsigned int ra;
unsigned int rb;
unsigned int rc;
rb=32;
while(1)
{
rb-=4;
rc=(d>>rb)&0xF;
if(rc>9) rc+=0x37; else rc+=0x30;
uart_putc(rc);
if(rb==0) break;
}
uart_putc(0x20);
}
static void hexstring ( unsigned int d )
{
hexstrings(d);
uart_putc(0x0D);
uart_putc(0x0A);
}
void soft_delay(void) {
for (volatile unsigned int i=0; i<2000000; ++i) { }
}
int notmain ( void )
{
PUT32(STK_CSR,4);
PUT32(STK_RVR,0x00FFFFFF);
PUT32(STK_CVR,0x00000000);
PUT32(STK_CSR,5);
uart_init();
hexstring(0x12345678);
hexstring(GET32(0xE000E018));
hexstring(GET32(0xE000E018));
return(0);
}
build
arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m3 flash.s -o flash.o
arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m3 test.s -o test.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -mthumb -mcpu=cortex-m0 -march=armv6-m -c notmain.c -o notmain.thumb.o
arm-none-eabi-ld -o notmain.thumb.elf -T flash.ld flash.o test.o notmain.thumb.o
arm-none-eabi-objdump -D notmain.thumb.elf > notmain.thumb.list
arm-none-eabi-objcopy notmain.thumb.elf notmain.thumb.bin -O binary
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -mthumb -mcpu=cortex-m3 -march=armv7-m -c notmain.c -o notmain.thumb2.o
arm-none-eabi-ld -o notmain.thumb2.elf -T flash.ld flash.o test.o notmain.thumb2.o
arm-none-eabi-objdump -D notmain.thumb2.elf > notmain.thumb2.list
arm-none-eabi-objcopy notmain.thumb2.elf notmain.thumb2.bin -O binary
uart output as shown
12345678
00FFE445
00FFC698
If I take your code, make it shorter, don't have all day.
void soft_delay(void) {
for (volatile unsigned int i=0; i<0x2000; ++i) { }
}
arm-none-eabi-gcc -c -O0 -mthumb -mcpu=cortex-m0 hello.c -o hello.o
yes I know this is an m3
arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 5.4.0
gives
00000000 <soft_delay>:
0: b580 push {r7, lr}
2: b082 sub sp, #8
4: af00 add r7, sp, #0
6: 2300 movs r3, #0
8: 607b str r3, [r7, #4]
a: e002 b.n 12 <soft_delay+0x12>
c: 687b ldr r3, [r7, #4]
e: 3301 adds r3, #1
10: 607b str r3, [r7, #4]
12: 687b ldr r3, [r7, #4]
14: 4a03 ldr r2, [pc, #12] ; (24 <soft_delay+0x24>)
16: 4293 cmp r3, r2
18: d9f8 bls.n c <soft_delay+0xc>
1a: 46c0 nop ; (mov r8, r8)
1c: 46bd mov sp, r7
1e: b002 add sp, #8
20: bd80 pop {r7, pc}
22: 46c0 nop ; (mov r8, r8)
24: 00001fff
first check the test infrastructure
.cpu cortex-m0
.thumb
.align 8
.word 0,0
.thumb_func
.globl TEST
TEST:
push {r4,r5,r6,lr}
mov r4,r0
mov r5,r1
ldr r6,[r4]
inner:
bl soft_delay
sub r5,#1
bne inner
ldr r3,[r4]
sub r0,r6,r3
pop {r4,r5,r6,pc}
.align 8
soft_delay:
bx lr
in the openocd telnet window
reset halt
flash write_image erase notmain.thumb.elf
reset
gives
12345678
00001B59
7001 clocks, assuming the systick matches the cpu, thats 7001 arm clocks, 4 instructions per loop.
Step back note I aligned some things
08000108 <TEST>:
8000108: b570 push {r4, r5, r6, lr}
800010a: 1c04 adds r4, r0, #0
800010c: 1c0d adds r5, r1, #0
800010e: 6826 ldr r6, [r4, #0]
08000110 <inner>:
8000110: f000 f876 bl 8000200 <soft_delay>
8000114: 3d01 subs r5, #1
8000116: d1fb bne.n 8000110 <inner>
8000118: 6823 ldr r3, [r4, #0]
800011a: 1af0 subs r0, r6, r3
800011c: bd70 pop {r4, r5, r6, pc}
08000200 <soft_delay>:
8000200: 4770 bx lr
both loops are nicely aligned.
Now if I do this:
0800010a <TEST>:
800010a: b570 push {r4, r5, r6, lr}
800010c: 1c04 adds r4, r0, #0
800010e: 1c0d adds r5, r1, #0
8000110: 6826 ldr r6, [r4, #0]
08000112 <inner>:
8000112: f000 f875 bl 8000200 <soft_delay>
8000116: 3d01 subs r5, #1
8000118: d1fb bne.n 8000112 <inner>
800011a: 6823 ldr r3, [r4, #0]
800011c: 1af0 subs r0, r6, r3
800011e: bd70 pop {r4, r5, r6, pc}
Simply changing the alignment of the code that is supposed to be testing the code under test I now get:
00001F40
8000 ticks to do that loop 1000 times with that call with the code function under test still being aligned
08000200 <soft_delay>:
8000200: 4770 bx lr
The .align 8, in general don't use .align with a number on gnu its behavior does not translate across targets. .balign is better. Anyway I used it. The two words are because the align made TEST aligned, but inner is what I wanted aligned so I added two words to make it aligned.
.align 8
.word 0,0
nop
.thumb_func
.globl TEST
TEST:
push {r4,r5,r6,lr}
mov r4,r0
mov r5,r1
ldr r6,[r4]
inner:
bl soft_delay
sub r5,#1
bne inner
ldr r3,[r4]
sub r0,r6,r3
pop {r4,r5,r6,pc}
A little code review to make sure I didn't make a mistake here.
r0 is the systick current value register
r1 is the number of loops I want to run the code under test
The calling convention allows for r0-r3 to be clobbered so I need to move r0 and r1 to non-volatile registers (per the calling convention).
I want to sample the time the instruction before the loop and the instruction after.
so I need two registers for r0 and r1 and a register to store the begin time so r4,r5,r6 and that fits in nicely to have an even number of registers pushed on the stack. Have to preserve lr so we can return.
we can now safely call soft_delay in the loop, subtract the count, branch if not equal to inner, once the count is done read the timer in r3. from output above this is a down counter so subtract end from beginning, technically since this is a 24 bit counter I should and with 0x00FFFFFF to correctly do that subtraction, but because this isn't going to roll over I can assume out that operation. result/return value goes in r0, pop everything which includes popping the pc to do the return to the C calling function which prints out r0's value.
I think the test code is good.
reading the CPUID register
411FC231
So that means r1p1, while the TRM I am using is written for r2p1 you have to be very careful to use the right document but also sometimes use the current document or all the ones in between if available to see what changed.
ICode memory interface
Instruction fetches from Code memory space 0x00000000 to 0x1FFFFFFF
are performed over the 32-bit AHB-Lite bus. The Debugger cannot access
this interface. All fetches are word-wide. The number of instructions
fetched per word depends on the code running and the alignment of the
code in memory.
Sometimes in ARM TRMs you see the fetch info up top near the processor features, this tells me what I wanted to know.
08000112 <inner>:
8000112: f000 f875 bl 8000200 <soft_delay>
8000116: 3d01 subs r5, #1
8000118: d1fb bne.n 8000112 <inner>
this requires a fetch at 110, 114 and 118.
08000110 <inner>:
8000110: f000 f876 bl 8000200 <soft_delay>
8000114: 3d01 subs r5, #1
8000116: d1fb bne.n 8000110 <inner>
This a fetch at 110 and 114, but not one at 118, so that extra fetch could be our added clock. the m3 was the first publicly available one and it has a lot of features in the core that went away and similar ones came back. Some of the smaller cores fetch differently and you don't see this alignment issue. with bigger cores like full sized ones they fetch sometimes 4 or 8 instructions at a time and you have to change your alignment even more to hit the boundary but you can hit the boundary and since it is 2 or 4 clocks plus bus overhead for the extra fetch you can see those.
If I put two nops
nop
nop
.thumb_func
.globl TEST
TEST:
gives
08000114 <inner>:
8000114: f000 f874 bl 8000200 <soft_delay>
8000118: 3d01 subs r5, #1
800011a: d1fb bne.n 8000114 <inner>
800011c: 6823 ldr r3, [r4, #0]
800011e: 1af0 subs r0, r6, r3
8000120: bd70 pop {r4, r5, r6, pc}
gives
00001B59
So that's good we are back to that number, could try a few more to confirm but it appears that alignment is sensitive to our outer test loop, which is bad, but we can manage that, don't change it it won't affect the test. If I didn't care about alignment and had something like this:
void soft_delay(void) {
for (volatile unsigned int i=0; i<0x2000; ++i) { }
}
int notmain ( void )
{
unsigned int ra;
unsigned int beg;
unsigned int end;
PUT32(STK_CSR,4);
PUT32(STK_RVR,0x00FFFFFF);
PUT32(STK_CVR,0x00000000);
PUT32(STK_CSR,5);
uart_init();
hexstring(0x12345678);
beg=GET32(STK_CVR);
for(ra=0;ra<1000;ra++)
{
soft_delay();
}
end=GET32(STK_CVR);
hexstring((beg-end)&0x00FFFFFF);
return(0);
}
Then as I played with optimization options and I also played with using different compilers any change in the program/binary in front of the test loop would/could move the test loop changing its performance, in my simple example it was a 14% performance difference, that's massive if you are doing performance tests. letting the compiler take care of all this without us being in control the everything in front of the function under test could mess with the function under test, as written above the compiler might opt to inline the function rather than call it making an even more interesting situation as the test loop while probably not as clean as mine, certainly not if not optimized, but now the code under test is dynamic as options or alignments change.
I'm very happy you happened to be using this core/chip...
If I re-align inner and now mess with this
.align 8
nop
soft_delay:
bx lr
08000202 <soft_delay>:
8000202: 4770 bx lr
it's a single instruction which is fetched at 0x200 from what we have read and seem to be able to tell. wouldn't expect this to change anything and it didn't
00001B59
but now that we know what we know, we can use our experience to mess with this trivial Not interesting at all example.
.align 8
nop
soft_delay:
nop
bx lr
gives
00001F41
as expected. and we can have even more fun:
.align 8
.word 0,0
nop
.thumb_func
.globl TEST
TEST:
combined gives
08000112 <inner>:
8000112: f000 f876 bl 8000202 <soft_delay>
8000116: 3d01 subs r5, #1
8000118: d1fb bne.n 8000112 <inner>
08000202 <soft_delay>:
8000202: 46c0 nop ; (mov r8, r8)
8000204: 4770 bx lr
no surprise if you know what you are doing:
00002328
9000 clocks, 29% performance difference. we are literally talking about 5 (technically 6) instructions, same exact machine code and by simply changing alignment the performance can be 29% different, compiler and options have nothing to do with it, yet, have not even gotten there.
How can we expect to do any kind of performance evaluation of a program using the time the code a bunch of times in a loop method? We cant unless we know what we are doing, have an understanding of the architecture, etc.
Now as it should be obvious and reading the documentation I am using the internal 8Mhz clock, everything is derived from that so the systick times are not going to sometimes vary as you might see with dram for example. The LATENCY bits in the FLASH_ACR register should have defaulted to zero wait states for 0 < SYSCLK <- 24Mhz. If I were to bump up the clock above 24Mhz, the processor is running faster but the flash is now slower relative to the processor.
Without messing with the clocks and simply adding a wait state by changing the FLASH_ACR register to 0x31.
000032C6
12998 up from 9000, I didn't expect it to double necessarily and it didn't.
Hmm for fun make a PUT16 using strh, and
.thumb_func
.globl HOP
HOP:
bx r2
and
PUT16(0x2000010a,0xb570); // 800010a: b570 push {r4, r5, r6, lr}
PUT16(0x2000010c,0x1c04); // 800010c: 1c04 adds r4, r0, #0
PUT16(0x2000010e,0x1c0d); // 800010e: 1c0d adds r5, r1, #0
PUT16(0x20000110,0x6826); // 8000110: 6826 ldr r6, [r4, #0]
PUT16(0x20000112,0xf000); // 8000112: f000 f876 bl 8000202 <soft_delay>
PUT16(0x20000114,0xf876); // 8000112: f000 f876 bl 8000202 <soft_delay>
PUT16(0x20000116,0x3d01); // 8000116: 3d01 subs r5, #1
PUT16(0x20000118,0xd1fb); // 8000118: d1fb bne.n 8000112 <inner>
PUT16(0x2000011a,0x6823); // 800011a: 6823 ldr r3, [r4, #0]
PUT16(0x2000011c,0x1af0); // 800011c: 1af0 subs r0, r6, r3
PUT16(0x2000011e,0xbd70); // 800011e: bd70 pop {r4, r5, r6, pc}
PUT16(0x20000202,0x46c0); // 8000202: 46c0 nop ; (mov r8, r8)
PUT16(0x20000204,0x4770); // 8000204: 4770 bx lr
hexstring(HOP(STK_CVR,1000,0x2000010B));
gives
0000464B
and that was not at all expected. but is 18,000 basically
Putting ram to bed after this
PUT16(0x20000108,0xb570); // 800010a: b570 push {r4, r5, r6, lr}
PUT16(0x2000010a,0x1c04); // 800010c: 1c04 adds r4, r0, #0
PUT16(0x2000010c,0x1c0d); // 800010e: 1c0d adds r5, r1, #0
PUT16(0x2000010e,0x6826); // 8000110: 6826 ldr r6, [r4, #0]
PUT16(0x20000110,0xf000); // 8000112: f000 f876 bl 8000202 <soft_delay>
PUT16(0x20000112,0xf876); // 8000112: f000 f876 bl 8000202 <soft_delay>
PUT16(0x20000114,0x3d01); // 8000116: 3d01 subs r5, #1
PUT16(0x20000116,0xd1fb); // 8000118: d1fb bne.n 8000112 <inner>
PUT16(0x20000118,0x6823); // 800011a: 6823 ldr r3, [r4, #0]
PUT16(0x2000011a,0x1af0); // 800011c: 1af0 subs r0, r6, r3
PUT16(0x2000011c,0xbd70); // 800011e: bd70 pop {r4, r5, r6, pc}
PUT16(0x20000200,0x46c0); // 8000202: 46c0 nop ; (mov r8, r8)
PUT16(0x20000200,0x4770); // 8000204: 4770 bx lr
hexstring(HOP(STK_CVR,1000,0x20000109));
00002EDE
The machine code did not change because I moved both back by 2 so the relative address between them was the same. Note that bl is two separate instructions not one 32 bit one. You cant see this in the newer docs you need to go back to the original/early ARM ARM where it is explained. And it is easy to do experiments where you split the two instructions and put other stuff in between and they work just fine, because they are two separate instructions.
At this point the reader should be able to make a 2 instruction test loop, time it and dramatically change the performance of the execution of those two instructions on this platform using the same exact machine code.
So let's try the volatile loop that you wrote.
.align 8
soft_delay:
push {r7, lr}
sub sp, #8
add r7, sp, #0
mov r3, #0
str r3, [r7, #4]
b L12
Lc:
ldr r3, [r7, #4]
add r3, #1
str r3, [r7, #4]
L12:
ldr r3, [r7, #4]
ldr r2, L24
cmp r3, r2
bls Lc
nop
mov sp, r7
add sp, #8
pop {r7, pc}
nop
.align
L24: .word 0x1FFF
this is I believe the unoptimized -O0 version. starting off with one test loop
hexstring(TEST(STK_CVR,1));
experience, the times we are seeing will overflow our 24 bit counter and the results will be very strange or lead to false conclusions.
0001801F
98,000, quick check for safety:
.align
L24: .word 0x1F
0000019F
not bad that is on par with 256 times faster.
so we have some wiggle room in our test loop but not much try 10
hexstring(TEST(STK_CVR,10));
000F012D
98334 ticks per loop.
changing the alignment
08000202 <soft_delay>:
8000202: b580 push {r7, lr}
8000204: b082 sub sp, #8
gave the same result
000F012D
not unheard of, you can examine the differences if you want count through each instruction check fetch cycles, etc.
had I made the test:
soft_delay:
nop
nop
bx lr
its two fetch cycles no matter what the alignment or if I had left it bx lr with no nops as we saw so by simply having an odd number of instructions in the test then alignment won't affect the results on fetches along, but note that from what we know now had some other code in the program moved the outer timing/test loop that may have changed performance and the results may show a difference between two tests that were purely the timing code and not the code under test (read Michael Abrash).
The cortex-m3 is based on the armv7-m architecture. If I change the compiler from -mcpu=cortex-m0 (all cortex-m compatible so far) to -mcpu=cortex-m3 (not all cortex-m compatible will break on half of them) it produces a little bit less code.
.align 8
soft_delay:
push {r7}
sub sp, #12
add r7, sp, #0
movs r3, #0
str r3, [r7, #4]
b L12
Lc:
ldr r3, [r7, #4]
add r3, #1
str r3, [r7, #4]
L12:
ldr r3, [r7, #4]
/*14: f5b3 5f00 cmp.w r3, #8192 ; 0x2000*/
//cmp.w r3, #8192
.word 0x5f00f5b3
bcc Lc
nop
add r7, #12
mov sp, r7
pop {r7}
bx lr
000C80FB 81945 ticks for the code under test.
I hate unified syntax, that was a massive mistake, so I fumble along in legacy mode. thus the .word thing there in the middle.
As part of writing this I kinda messed up my system in order to demonstrate something. I was building a gcc 5.4.0 but overwrote my 9.2.0 so had to re-build both.
2.95 was the version I started using with arm and didn't support thumb gcc 3.x.x was the first to. And either gcc 4.x.x or gcc 5.x.x produced "slower" code for some of my projects, at work we are currently moving from ubuntu 16.04 to 18.04 for our build systems which if you use the apt-got cross compiler for arm that moves you from 5.x.x to 7.x.x and it is making larger binaries for the same source code and where we are tight on memory it is pushing us beyond what's available so we have to either remove some code (easiest to make the printed messages shorter, cut text out) or stick to the older compiler by building our own or apt-getting the older one. 19.10 does no longer offers the 5.x.x version.
So both are now built.
18: d3f8 bcc.n c <soft_delay+0xc>
1a: bf00 nop
1c: bf00 nop
1e: 370c adds r7, #12
these nops after bcc are baffling to me...
18: d3f8 bcc.n c <soft_delay+0xc>
1a: bf00 nop
1c: 370c adds r7, #12
gcc 5.4.0 is putting one, gcc 9.2.0 is putting two nops, ARM doesn't have the branch shadow thing of MIPS (MIPS doesn't currently either).
000C80FB gcc 5.4.0
000C8105 gcc 9.2.0
I call the function 10 times, the nop is outside the code under tests loop so has a lesser effect.
Optimized all cortex-m variants (to date) using gcc 9.2.0
soft_delay:
mov r3, #0
mov r2, #128
sub sp, #8
str r3, [sp, #4]
ldr r3, [sp, #4]
lsl r2, r2, #6
cmp r3, r2
bcs L1c
L10:
ldr r3, [sp, #4]
add r3, #1
str r3, [sp, #4]
ldr r3, [sp, #4]
cmp r3, r2
bcc L10
L1c:
add sp, #8
bx lr
(also understand that not all say gcc 9.2.0 builds produce the same code when you build the compiler you have options and those options can affect the output making different builds of 9.2.0 possibly producing different results)
000C80B5
gcc 9.2.0 built for cortex-m3:
soft_delay:
mov r3, #0
sub sp, #8
str r3, [sp, #4]
ldr r3, [sp, #4]
/*8: f5b3 5f00 cmp.w r3, #8192 ; 0x2000*/
.word 0x5F00F5B3
bcs L1c
Le:
ldr r3, [sp, #4]
add r3, #1
str r3, [sp, #4]
ldr r3, [sp, #4]
/*16: f5b3 5f00 cmp.w r3, #8192 ; 0x2000*/
.word 0x5F00F5B3
bcc Le
L1c:
add sp, #8
bx lr
000C80A1
That's in the noise. despite the code built has differences. they simply didn't gain in comparing the 0x2000 in fewer instructions. and note if you change that 0x2000 to some other number then that does not simply make the loop take that much longer it can change the generated code for architectures like this.
How I like to make these counted delay loops is to use a function outside the compile domain
extern void dummy ( unsigned int );
void soft_delay(void) {
for (unsigned int i=0; i<0x2000; ++i) { dummy(i); }
}
soft_delay:
push {r4, r5, r6, lr}
mov r5, #128
mov r4, #0
lsl r5, r5, #6
L8:
mov r0, r4
add r4, #1
bl dummy
cmp r4, r5
bne L8
pop {r4, r5, r6, pc}
the feature there is you don't need the overhead of what volatile does you do have a call and clearly there is overhead as well due to the call but not as much
000B40C9
or even better:
soft_delay:
sub r0,#1
bne soft_delay
bx lr
I would have to change the code wrapped around the code under test to make that function work.
Another note specific to these targets but also something you deal with
unsigned int more_fun ( unsigned int, unsigned int );
unsigned int fun ( unsigned int a, unsigned int b )
{
return(more_fun(a,b)+a+(b<<2));
}
00000000 <fun>:
0: b570 push {r4, r5, r6, lr}
2: 000c movs r4, r1
4: 0005 movs r5, r0
6: f7ff fffe bl 0 <more_fun>
a: 00a4 lsls r4, r4, #2
c: 1964 adds r4, r4, r5
e: 1820 adds r0, r4, r0
10: bd70 pop {r4, r5, r6, pc}
12: 46c0 nop ; (mov r8, r8)
a question repeated here at SO on a period basis. why is it pushing r6 it isn't using r6.
The compiler operates using what I call and used to be called a calling convention, now they use terms ABI, EABI, whatever either case it is the same thing it is a set of rules the compiler follows for a particular target. Arm added a rule to keep the stack aligned on a 64 bit address boundary instead of 32, this caused the extra item to keep the stack aligned, what register is used there can vary. If you use an older gcc vs a newer this can/will affect the performance of your code all by itself.
There are many factors at play here. Certainly if you have an optimizing compiler and you compare optimized vs not DEPENDING ON THE CODE you can see a large difference in execution speed. Using the volatile in the tiny loop here actually masks some of that, in both cases it should be read/written to memory every loop.
But the calling code the loop variable unoptimized would touch ram two or three times in that loop, optimized ideally would be in a register the whole time, making for a dramatic difference in execution performance even with zero wait state ram.
The toggle pin code is relatively large (talking to the peripheral directly would be less code), depending on whether that library was compiled separately with different options or at the same time with the same options makes a big difference with respect to performance.
Add that this is an mcu and running off of a flash which with the age of this part the flash might at best be half the clock rate of the cpu and worst a number of wait states and I don't remember off hand if ST had the caching in front of it at that time. so every instruction you add can add a clock, so just the loop variable alone can dramatically change the timing.
Being a high performance pipelined core I have demonstrated here and elsewhere that alignment can (not always) play a role, so if in one case the exact same machine code links to address 0x100 in one case and 0x102 in another it is possible that exact same machine code takes extra or fewer clocks to execute based on the nature of the pre-fetcher in the design, or the flash implementation, cache if any implementation, branch predictor, etc.
And then the biggest problem is how did you time this, it is not uncommon for there to be error in not using a clock correctly such that the clock/timing code itself varies and is creating some of the difference. Plus are there background things going on, interrupts/multitasking.
Michal Abrash wrote a wonderful book called The Zen of Assembly Language, you can get it for free in ePub or perhaps pdf form on GitHub. the 8088 was obsolete when the book was released but if you focus on that then you have missed the point, I bought it when it came out and have used what I learned on nearly a daily basis.
gcc is not a high performance compiler it is more of a general purpose compiler built Unix style where you can have different language front ends and different target backends. When I was in the position you are now trying to first understand these things I sampled many compilers for the same arm target and same C code and the results were vast. Later I wrote an instruction set simulator so I could count instructions and memory accesses to compare gnu vs llvm, as the latter has more optimization opportunities than gnu but for execution tests of code gcc was faster sometimes but not slower. That ended up being more of a long weekend having fun than something I used to analyze the differences.
It is easier to start with small-ish code like this and disassemble the two. Understand that fewer instructions doesn't mean faster, one distant memory access on a dram based system can take hundreds of clock cycles, that might be replaced with another solution that takes a handful/dozen of linearly fetched instructions to end up with the same result (do some math vs look up something in a rarely sampled table) and depending on the situation the dozen instructions execute much faster. at the same time the table solution can be much faster. it depends.
Examination of the disassembly often leads to incorrect conclusions (read abrash, not just that book, everything) as first off folks think less instructions means faster code. rearranging instructions in a pipelined processor can improve performance if you move an instruction into a time period that would have otherwise been wasted clocks. incrementing a register not related to a memory access in front of the memory access instead of after in a non-superscaler processor.
Ahh, back to a comment. This was years ago and competing compilers were more of a thing most folks just wrap their gui around gnu and the ide/gui is the product not the compiler. But there was the arm compiler itself, before the rvct tools, ads and I forget the other, those were "better" than gcc. I forget the names of the others but there was one that produced significantly faster code, granted this was Dhrystone so you will also find that they may tune optimizers for Dhrystone just to play benchmark games. Now that I can see how easy it is to manipulate benchmarks I consider them to in general be bu33zzit, can't be trusted. Kiel used to be a multi-target tool for mcus and similar, but then was purchased by arm and I thought at the time they were dropping all other targets, but have not checked in a while. I might have tried them once to get access to a free/demo version of rvct as when I was working at one job we had a budget to buy multi-thousand dollar tools, but that didn't include rvct (although I was on phone calls with the formerly Allant folks who were part of a purchase that became the rvct tools) which I was eager to try once they had finished the development/integration of that product, by then didn't have a budget for that and later couldn't afford or wasn't interested in buying even kiels tools, much less arms. Their early demos of rvct created an encrypted/obfuscated binary that was not arm machine code it only ran on their simulator so you couldn't use it to eval performance or to compare it to others, don't think they were willing to give us an un-obfuscated version and we weren't willing to reverse engineer it. now it is easier just to use gcc or clang and hand optimize where NEEDED. Likewise with experience can write C code that optimizes better based on experience examining compiler output.
You have to know the hardware, particularly in this case where you take processor IP and most of the chip is not related to the processor IP and most of the performance is not related to the processor IP (pretty much true for a lot of platforms today in particular your server/desktop/laptop). The Gameboy Advance for example used a lot of 16 bit buses instead of 32, thumb tools were barely being integrated, but thumb mode while counting instructions/or bytes was like 10% more code at the time, executed significantly faster on that chip. On other implementations both arm architecture and chip design thumb may have performance penalties, or not.
ST in general with the cortex-m products tends to put a cache in front of the flash, sometimes they document it and provide enable/disable control sometimes not so it can be difficult at best to get a real performance value as the typical thing is to run the code under test many times in a loop so you can get a better time measurement. other vendors don't necessarily do this and it is much easier to see the flash wait states and get a real, worst case, timing value that you can use to validate your design. caches in general as well as pipelines make it difficult at best to get good, repeatable, reliable numbers to validate your design. So for example you sometimes cannot do the alignment trick to mess with performance of the same machine code on an st but on say a ti with the same core you can. st might not give you the icache in a cortex-m7 where another vendor might since st has already covered that. Even within one brand name though don't expect the results of one chip/family to translate to another chip/family even if they use the same core. Also look at subtle comments in the arm documentation as to whether some cores offer a fetch size as an advertised option, single or multi-cycle multiply, etc. and I'll tell you that there are other compile time options for the core that are not shown in the technical reference manual that can affect performance so don't assume that all cortex-m3s are the same even if they are the same revision from arm. The chip vendor has the source so they can go even further and modify it, or for example a register bank to be implemented by the consumer they might change it from no protection to parity to ecc which might affect performance while retaining all of arms original code as is. When you look at an avr or pic though or even an msp430, while I cant prove it those designs appear more static not tiny vs Xmega vs regular old avr as there are definite differences there, but one tiny to another.
Your assumptions are a good start, there really isn't such a thing as over-optimization, more of a thing of missed optimizations, but there may be other factors you are not seeing in your assumptions that may or may not be see in a disassembly. there are obvious things that we would expect like one of the loop variables to be register based vs memory based. alignment, I wouldn't expect the clock settings to change if you used the same code, but a different set of experiments using timers or a scope you can measure the clock settings to see if they were configured the same. background tasks, interrupts and dumb luck as to how/when they hit the test. But bottom line, sometimes it is as simple as a missed optimization or subtle differences in how one compiler generates code to another, it is as often not those things and more of a system issue, memory speed, peripheral speed, caches or their architecture, how the various busses in the design operate, etc. For some of these cortex-ms (and many other processors) you can exploit their bus behavior to show a performance difference in something that the average person wouldn't expect to see.
Keil over-optimization (unlikely, because the code is very simple)
You cant over-optimize you can under/miss so if anything gcc missed something that Kiel didn't. Not the other way around
arm-none-eabi-gcc under-optimization due to wrong compiler flags (I use CLion Embedded plugins` CMakeLists.txt)
will see below but this is highly likely esp debug vs release, I never build for debug (never use a debugger) you have to test everything twice, and if you don't test as you go it makes it much harder to debug so the release version if it has issues takes a lot more work to figure out the issues.
A bug in the initialization so that chip has lower clock frequency with arm-none-eabi-gcc (to be investigated)
My guess is it isn't this, this would imply you made a really big mistake and didn't compile the same code on each tool so it wasn't a fair comparison.
Let's run it.
Using the systick timer, 24 bit (current value register address passed in r0)
.align 8
.thumb_func
.globl TEST
TEST:
push {r4,r5,r6,lr}
mov r4,r0
ldr r5,[r4]
bl soft_delay
ldr r3,[r4]
sub r0,r5,r3
pop {r4,r5,r6,pc}
to avoid overflowing the 24 bit timer the loops count to limited to 200000 times not 2000000 times. I assume the code you left out is 2000000 - 1. If not this still shows the relevant differences.
-O0 code
.align 8
soft_delay:
PUSH {r3,lr}
MOV r0,#0
STR r0,[sp,#0]
B L6.14
L6.8:
LDR r0,[sp,#0]
ADD r0,r0,#1
STR r0,[sp,#0]
L6.14:
LDR r1,L6.24
LDR r0,[sp,#0]
CMP r0,r1
BCC L6.8
POP {r3,pc}
.align
L6.24: .word 100000 - 1
08000200 <soft_delay>:
8000200: b508 push {r3, lr}
8000202: 2000 movs r0, #0
8000204: 9000 str r0, [sp, #0]
8000206: e002 b.n 800020e <L6.14>
08000208 <L6.8>:
8000208: 9800 ldr r0, [sp, #0]
800020a: 3001 adds r0, #1
800020c: 9000 str r0, [sp, #0]
0800020e <L6.14>:
800020e: 4902 ldr r1, [pc, #8] ; (8000218 <L6.24>)
8000210: 9800 ldr r0, [sp, #0]
8000212: 4288 cmp r0, r1
8000214: d3f8 bcc.n 8000208 <L6.8>
8000216: bd08 pop {r3, pc}
08000218 <L6.24>:
8000218: 0001869f
00124F8B systick timer ticks
-O1 code
soft_delay:
PUSH {r3,lr}
MOV r0,#0
STR r0,[sp,#0]
LDR r0,L6.24
B L6.16
L6.10:
LDR r1,[sp,#0]
ADD r1,r1,#1
STR r1,[sp,#0]
L6.16:
LDR r1,[sp,#0]
CMP r1,r0
BCC L6.10
POP {r3,pc}
.align
L6.24: .word 100000 - 1
08000200 <soft_delay>:
8000200: b508 push {r3, lr}
8000202: 2000 movs r0, #0
8000204: 9000 str r0, [sp, #0]
8000206: 4804 ldr r0, [pc, #16] ; (8000218 <L6.24>)
8000208: e002 b.n 8000210 <L6.16>
0800020a <L6.10>:
800020a: 9900 ldr r1, [sp, #0]
800020c: 3101 adds r1, #1
800020e: 9100 str r1, [sp, #0]
08000210 <L6.16>:
8000210: 9900 ldr r1, [sp, #0]
8000212: 4281 cmp r1, r0
8000214: d3f9 bcc.n 800020a <L6.10>
8000216: bd08 pop {r3, pc}
08000218 <L6.24>:
8000218: 0001869f
000F424E systicks
-O2 code
soft_delay:
SUB sp,sp,#4
MOVS r0,#0
STR r0,[sp,#0]
LDR r0,L4.24
L4.8:
LDR r1,[sp,#0]
ADDS r1,r1,#1
STR r1,[sp,#0]
CMP r1,r0
BCC L4.8
ADD sp,sp,#4
BX lr
.align
L4.24: .word 100000 - 1
08000200 <soft_delay>:
8000200: b081 sub sp, #4
8000202: 2000 movs r0, #0
8000204: 9000 str r0, [sp, #0]
8000206: 4804 ldr r0, [pc, #16] ; (8000218 <L4.24>)
08000208 <L4.8>:
8000208: 9900 ldr r1, [sp, #0]
800020a: 3101 adds r1, #1
800020c: 9100 str r1, [sp, #0]
800020e: 4281 cmp r1, r0
8000210: d3fa bcc.n 8000208 <L4.8>
8000212: b001 add sp, #4
8000214: 4770 bx lr
8000216: 46c0 nop ; (mov r8, r8)
08000218 <L4.24>:
8000218: 0001869f
000AAE65 systicks
-O3
soft_delay:
PUSH {r3,lr}
MOV r0,#0
STR r0,[sp,#0]
LDR r0,L5.20
L5.8:
LDR r1,[sp,#0]
ADD r1,r1,#1
STR r1,[sp,#0]
CMP r1,r0
BCC L5.8
POP {r3,pc}
.align
L5.20: .word 100000 - 1
08000200 <soft_delay>:
8000200: b508 push {r3, lr}
8000202: 2000 movs r0, #0
8000204: 9000 str r0, [sp, #0]
8000206: 4803 ldr r0, [pc, #12] ; (8000214 <L5.20>)
08000208 <L5.8>:
8000208: 9900 ldr r1, [sp, #0]
800020a: 3101 adds r1, #1
800020c: 9100 str r1, [sp, #0]
800020e: 4281 cmp r1, r0
8000210: d3fa bcc.n 8000208 <L5.8>
8000212: bd08 pop {r3, pc}
08000214 <L5.20>:
8000214: 0001869f
000AAE6A systicks
Interestingly alignment doesn't affect any of these results.
Comparing your results relative to each other and the above in a spreadsheet
18.7 1.000 00124F8B 1200011 1.000
13.3 0.711 000F424E 1000014 0.833
9.8 0.524 000AAE65 700005 0.583
9.9 0.529 000AAE6A 700010 0.583
It shows that the various stages as I have measured also show improvements and that -O3 is slightly slower.
Analyze what happened.
void soft_delay(void) {
for (volatile uint32_t i=0; i<2000000; ++i) { }
}
because this counts up AND is volatile the compiler cannot do the usual count down and save an instruction (subs then bne rather than add, cmp, bcc)
-O0 code
soft_delay:
PUSH {r3,lr} allocate space for i
MOV r0,#0 i = 0
STR r0,[sp,#0] i = 0
B L6.14
L6.8:
LDR r0,[sp,#0] read i from memory
ADD r0,r0,#1 increment i
STR r0,[sp,#0] save i to memory
L6.14:
LDR r1,L6.24 read max value
LDR r0,[sp,#0] read i from memory
CMP r0,r1 compare i and max value
BCC L6.8 branch if unsigned lower
POP {r3,pc} return
I should have examined the code first L6.24 should have been 2000000 not 2000000 - 1. You left this out of your question.
No optimization generally means just bang out the code in order as in the high level language.
r3 doesn't need to be preserved neither does LR but the variable is volatile so it needs space on the stack the compiler chose to do it this way for this optimization level pushing lr allows for it to pop pc at the end.
push is a pseudo instruction for stm (stmdb) so 8 is subtracted from the stack pointer then the registers are saved in order so if the sp was at 0x1008 then it changes to 0x1000 and writes r3 to 0x1000 and lr to 0x1004 so for the rest of this function it uses sp+0 which is 0x1000 in this example. The r3 and the push used in this way is to allocate a location for the variable i in the code.
-O1 version
soft_delay:
PUSH {r3,lr} allocate space
MOV r0,#0 i = 0
STR r0,[sp,#0] i = 0
LDR r0,L6.24 read max/test value
B L6.16
L6.10:
LDR r1,[sp,#0] load i from memory
ADD r1,r1,#1 increment i
STR r1,[sp,#0] save i to memory
L6.16:
LDR r1,[sp,#0] read i from memory
CMP r1,r0 compare i with test value
BCC L6.10 branch if unsigned lower
POP {r3,pc}
The primary difference between -O0 and -O1 in this case is the -O0 version reads the max value every time through the loop. The -O1 version reads it outside the loop one time.
-O0
08000208 <L6.8>:
8000208: 9800 ldr r0, [sp, #0]
800020a: 3001 adds r0, #1
800020c: 9000 str r0, [sp, #0]
800020e: 4902 ldr r1, [pc, #8] ; (8000218 <L6.24>)
8000210: 9800 ldr r0, [sp, #0]
8000212: 4288 cmp r0, r1
8000214: d3f8 bcc.n 8000208 <L6.8>
1200011 / 100000 = 12
The bulk of the time is in the above loop. 7 instructions three loads two stores. That is 12 things so perhaps its one clock per.
-O1 code
0800020a <L6.10>:
800020a: 9900 ldr r1, [sp, #0]
800020c: 3101 adds r1, #1
800020e: 9100 str r1, [sp, #0]
08000210 <L6.16>:
8000210: 9900 ldr r1, [sp, #0]
8000212: 4281 cmp r1, r0
8000214: d3f9 bcc.n 800020a <L6.10>
1000014 / 100000 = 10
0800020a <L6.10>:
800020a: 9900 ldr r1, [sp, #0]
800020c: 3101 adds r1, #1
800020e: 9100 str r1, [sp, #0]
8000210: 9900 ldr r1, [sp, #0]
8000212: 4281 cmp r1, r0
8000214: d3f9 bcc.n 800020a <L6.10>
6 instructions, two loads one store. 8 things 10 clocks. The difference here from -O0 is that the compare value is read before/outside the loop so that saves that instruction and that memory cycle.
-O2 code
08000208 <L4.8>:
8000208: 9900 ldr r1, [sp, #0]
800020a: 3101 adds r1, #1
800020c: 9100 str r1, [sp, #0]
800020e: 4281 cmp r1, r0
8000210: d3fa bcc.n 8000208 <L4.8>
700005 / 100000 = 7 ticks per loop
So by some folks definition, this isn't honoring the volatile, or is it? The compare value is outside the loop and the way this is written it should be 2000000 + 1, yes? It reads i from memory one time per loop rather than twice but does store it every time through the loop with the new value. Basically it removed the second load and that saved some time waiting on that read to finish.
-O3 code
08000208 <L5.8>:
8000208: 9900 ldr r1, [sp, #0]
800020a: 3101 adds r1, #1
800020c: 9100 str r1, [sp, #0]
800020e: 4281 cmp r1, r0
8000210: d3fa bcc.n 8000208 <L5.8>
The inner loop is the same as -O2.
-O2 does this
08000200 <soft_delay>:
8000200: b081 sub sp, #4
8000202: 2000 movs r0, #0
8000204: 9000 str r0, [sp, #0]
8000206: 4804 ldr r0, [pc, #16] ; (8000218 <L4.24>)
...
8000212: b001 add sp, #4
8000214: 4770 bx lr
-O3 does this
08000200 <soft_delay>:
8000200: b508 push {r3, lr}
8000202: 2000 movs r0, #0
8000204: 9000 str r0, [sp, #0]
8000206: 4803 ldr r0, [pc, #12] ; (8000214 <L5.20>)
8000212: bd08 pop {r3, pc}
Now that is fewer instructions yes, but the push and pop take longer they have memory cycle overhead, the subtract and add of the stack pointer instructions are faster than those memory cycles even with the fewer instructions. So the subtle difference in time is the push/pop outside the loop.
Now for GCC (9.2.0)
For starters I don't know if Kiel was targetted at thumb in general (all variants) the cortex-ms or the cortex-m3 specifically.
First -O0 code:
-O0
soft_delay:
push {r7, lr}
sub sp, sp, #8
add r7, sp, #0
movs r3, #0
str r3, [r7, #4]
b .L2
.L3:
ldr r3, [r7, #4]
adds r3, r3, #1
str r3, [r7, #4]
.L2:
ldr r3, [r7, #4]
ldr r2, .L4
cmp r3, r2
bls .L3
nop
nop
mov sp, r7
add sp, sp, #8
# sp needed
pop {r7}
pop {r0}
bx r0
.L5:
.align 2
.L4:
.word 199999
08000200 <soft_delay>:
8000200: b580 push {r7, lr}
8000202: b082 sub sp, #8
8000204: af00 add r7, sp, #0
8000206: 2300 movs r3, #0
8000208: 607b str r3, [r7, #4]
800020a: e002 b.n 8000212 <soft_delay+0x12>
800020c: 687b ldr r3, [r7, #4]
800020e: 3301 adds r3, #1
8000210: 607b str r3, [r7, #4]
8000212: 687b ldr r3, [r7, #4]
8000214: 4a04 ldr r2, [pc, #16] ; (8000228 <soft_delay+0x28>)
8000216: 4293 cmp r3, r2
8000218: d9f8 bls.n 800020c <soft_delay+0xc>
800021a: 46c0 nop ; (mov r8, r8)
800021c: 46c0 nop ; (mov r8, r8)
800021e: 46bd mov sp, r7
8000220: b002 add sp, #8
8000222: bc80 pop {r7}
8000224: bc01 pop {r0}
8000226: 4700 bx r0
8000228: 00030d3f andeq r0, r3, pc, lsr sp
00124F9F
Immediately we see two things, first the stack frame which Kiel was not building and second these mystery nops after the compare, gotta be some chip errata or something, need to look that up. From my other answer that may be deleted by now gcc 5.4.0 put one nop, tcc 9.2.0 put two. so this loop has
1200031 / 100000 = 12 ticks per loop
800020c: 687b ldr r3, [r7, #4]
800020e: 3301 adds r3, #1
8000210: 607b str r3, [r7, #4]
8000212: 687b ldr r3, [r7, #4]
8000214: 4a04 ldr r2, [pc, #16] ; (8000228 <soft_delay+0x28>)
8000216: 4293 cmp r3, r2
8000218: d9f8 bls.n 800020c <soft_delay+0xc>
The main loop where this code spends its time is also 12 ticks like Kiel its the same just different registers which don't matter. The subtle overall time difference is that the stack frame and the extra nops make the gcc version slightly longer.
arm-none-eabi-gcc -O0 -fomit-frame-pointer -c -mthumb -mcpu=cortex-m0 hello.c -o hello.o
arm-none-eabi-objdump -D hello.o > hello.list
arm-none-eabi-gcc -O0 -fomit-frame-pointer -S -mthumb -mcpu=cortex-m0 hello.c
If I build without a frame pointer then gcc -O0 becomes
soft_delay:
sub sp, sp, #8
movs r3, #0
str r3, [sp, #4]
b .L2
.L3:
ldr r3, [sp, #4]
adds r3, r3, #1
str r3, [sp, #4]
.L2:
ldr r3, [sp, #4]
ldr r2, .L4
cmp r3, r2
bls .L3
nop
nop
add sp, sp, #8
bx lr
.L5:
.align 2
.L4:
.word 99999
08000200 <soft_delay>:
8000200: b082 sub sp, #8
8000202: 2300 movs r3, #0
8000204: 9301 str r3, [sp, #4]
8000206: e002 b.n 800020e <soft_delay+0xe>
8000208: 9b01 ldr r3, [sp, #4]
800020a: 3301 adds r3, #1
800020c: 9301 str r3, [sp, #4]
800020e: 9b01 ldr r3, [sp, #4]
8000210: 4a03 ldr r2, [pc, #12] ; (8000220 <soft_delay+0x20>)
8000212: 4293 cmp r3, r2
8000214: d9f8 bls.n 8000208 <soft_delay+0x8>
8000216: 46c0 nop ; (mov r8, r8)
8000218: 46c0 nop ; (mov r8, r8)
800021a: b002 add sp, #8
800021c: 4770 bx lr
800021e: 46c0 nop ; (mov r8, r8)
8000220: 0001869f
00124F94
and saves 11 clocks over the other gcc version unlike Kiel gcc is not doing the push pop thing so saving some clocks over Kiel but the nops don't help.
Update: I had the wrong number of loops for Kiel because it used unsigned lower instead of unsigned lower or same as with gcc. Even the playing field, remove the nops fix the loops gcc is 00124F92 and Kiel 00124F97 5 clocks slower due to the push/pop vs sp math. gcc 5.4.0 also does the sp math thing, with the nop 00124F93. Being outside the loop stuff these differences while measurable are also in the noise when comparing these two (three) compilers.
gcc -O1
soft_delay:
sub sp, sp, #8
mov r3, #0
str r3, [sp, #4]
ldr r2, [sp, #4]
ldr r3, .L5
cmp r2, r3
bhi .L1
mov r2, r3
.L3:
ldr r3, [sp, #4]
add r3, r3, #1
str r3, [sp, #4]
ldr r3, [sp, #4]
cmp r3, r2
bls .L3
.L1:
add sp, sp, #8
bx lr
.L6:
.align 2
.L5:
.word 99999
08000200 <soft_delay>:
8000200: b082 sub sp, #8
8000202: 2300 movs r3, #0
8000204: 9301 str r3, [sp, #4]
8000206: 9a01 ldr r2, [sp, #4]
8000208: 4b05 ldr r3, [pc, #20] ; (8000220 <soft_delay+0x20>)
800020a: 429a cmp r2, r3
800020c: d806 bhi.n 800021c <soft_delay+0x1c>
800020e: 1c1a adds r2, r3, #0
8000210: 9b01 ldr r3, [sp, #4]
8000212: 3301 adds r3, #1
8000214: 9301 str r3, [sp, #4]
8000216: 9b01 ldr r3, [sp, #4]
8000218: 4293 cmp r3, r2
800021a: d9f9 bls.n 8000210 <soft_delay+0x10>
800021c: b002 add sp, #8
800021e: 4770 bx lr
8000220: 0001869f muleq r1, pc, r6 ; <UNPREDICTABLE>
000F4251
10 ticks per loop
8000210: 9b01 ldr r3, [sp, #4]
8000212: 3301 adds r3, #1
8000214: 9301 str r3, [sp, #4]
8000216: 9b01 ldr r3, [sp, #4]
8000218: 4293 cmp r3, r2
800021a: d9f9 bls.n 8000210 <soft_delay+0x10>
Same as Kiel the load of the compare value is outside the loop now saving a little per loop. It was architected a little different. And I believe the nops after the bls are something else. I just saw someone asking about why gcc did something that another didn't what seemed to be an extra instruction. I would use the term missed optimization vs bug, but either way this one doesn't have the nops...
gcc -O2 code
soft_delay:
mov r3, #0
sub sp, sp, #8
str r3, [sp, #4]
ldr r3, [sp, #4]
ldr r2, .L7
cmp r3, r2
bhi .L1
.L3:
ldr r3, [sp, #4]
add r3, r3, #1
str r3, [sp, #4]
ldr r3, [sp, #4]
cmp r3, r2
bls .L3
.L1:
add sp, sp, #8
bx lr
.L8:
.align 2
.L7:
.word 99999
08000200 <soft_delay>:
8000200: 2300 movs r3, #0
8000202: b082 sub sp, #8
8000204: 9301 str r3, [sp, #4]
8000206: 9b01 ldr r3, [sp, #4]
8000208: 4a05 ldr r2, [pc, #20] ; (8000220 <soft_delay+0x20>)
800020a: 4293 cmp r3, r2
800020c: d805 bhi.n 800021a <soft_delay+0x1a>
800020e: 9b01 ldr r3, [sp, #4]
8000210: 3301 adds r3, #1
8000212: 9301 str r3, [sp, #4]
8000214: 9b01 ldr r3, [sp, #4]
8000216: 4293 cmp r3, r2
8000218: d9f9 bls.n 800020e <soft_delay+0xe>
800021a: b002 add sp, #8
800021c: 4770 bx lr
800021e: 46c0 nop ; (mov r8, r8)
8000220: 0001869f
000F4251
No difference from -O1
800020e: 9b01 ldr r3, [sp, #4]
8000210: 3301 adds r3, #1
8000212: 9301 str r3, [sp, #4]
8000214: 9b01 ldr r3, [sp, #4]
8000216: 4293 cmp r3, r2
8000218: d9f9 bls.n 800020e <soft_delay+0xe>
gcc is not willing to take that second load out of the loop.
at the -O2 level Kiel is 70005 ticks and gcc 1000017. 42 percent more/slower.
gcc -O3 produced the same code as -O2.
So the key difference here is perhaps an interpretation of what volatile does, and there are some folks at SO that get upset about its use anyway, but let's just assume that it means everything you do with the variable needs to go to/from memory.
From what I normally see that means this
.L3:
ldr r3, [sp, #4]
add r3, r3, #1
str r3, [sp, #4]
ldr r3, [sp, #4]
cmp r3, r2
bls .L3
not this
.L3:
ldr r3, [sp, #4]
add r3, r3, #1
str r3, [sp, #4]
cmp r3, r2
bls .L3
Is that a Kiel bug? Do you want to use your over-optimization term here?
There are two operations an increment
ldr r3, [sp, #4]
add r3, r3, #1
str r3, [sp, #4]
and a compare
ldr r3, [sp, #4]
cmp r3, r2
bls .L3
arguably each should access the variable from memory not from a register. (in a pure debug version sense you should see code like this too btw, although the tool defines what it means by debug version)
When you figure out which gcc you have and how it was used it may account for even more code on the gcc side being the 100% slower not 40%.
I don't know that you could make this any tighter, I don't think re-arranging instructions will improve performance either.
Also, this was a missed optimization in gcc:
cmp r3, r2
bhi .L1
gcc knew that it was starting from zero and knew it was going to a bigger number so r3 would never be larger than r2 here.
We wish for the tool to make this:
soft_delay:
mov r3, #0
ldr r2, .L7
.L3:
add r3, r3, #1
cmp r3, r2
bls .L3
.L1:
bx lr
.L8:
.align 2
.L7:
.word 99999
00061A88
at 4 instructions per loop on average
but without the volatile it is dead code so the optimizer would simply remove it rather than make this code. A down count loop would be slightly smaller
soft_delay:
ldr r2, .L7
.L3:
sub r2, r2, #1
bne .L3
.L1:
bx lr
.L8:
.align 2
.L7:
.word 100000
000493E7
3 ticks per loop, removing the extra instruction helped.
Keil over-optimization (unlikely, because the code is very simple)
You might actually be right here, not because it is simple, but what does volatile really mean, and is it subject to interpretation by the compilers (I would have to find a spec). Is this a Kiel bug, did it over optimize?
There still isn't such a thing as over-optimization, there is a name for that, a compiler bug. So did Kiel interpret this wrong or Kiel and gcc disagree on the interpretation of volatile.
arm-none-eabi-gcc under-optimization due to wrong compiler flags (I use CLion Embedded plugins` CMakeLists.txt)
This could be it as well, for the same reason. Is this simply an "implementation defined" difference between compilers and both are right based on their definition?
Now gcc did miss an optimization here (or two), but it accounts for a small amount as it is outside the loop.
GCC ||| KEIL
|||
soft_delay: |||
mov r3, #0 |||
sub sp, sp, #8 |||
str r3, [sp, #4] |||
ldr r3, [sp, #4] |||
ldr r2, .L7 |||
cmp r3, r2 |||
bhi .L1 ||| soft_delay PROC
.L3: ||| PUSH {r3,lr}
ldr r3, [sp, #4] ||| MOVS r0,#0
add r3, r3, #1 ||| STR r0,[sp,#0]
str r3, [sp, #4] ||| LDR r0,|L5.20|
ldr r3, [sp, #4] ||| |L5.8|
cmp r3, r2 ||| LDR r1,[sp,#0]
bls .L3 ||| ADDS r1,r1,#1
.L1: ||| STR r1,[sp,#0]
add sp, sp, #8 ||| CMP r1,r0
bx lr ||| BCC |L5.8|
.L7: ||| POP {r3,pc}
.word 1999999 ||| ENDP
There is obvious bug in KEIL. volatile means that its value has to be loaded before every use and saved when changed. ? Keil is missing one load.
The variable is used 2 times: 1: when increased, 2: when compared. Two loads needed.

ARM Assembly Arrays

I am trying to figure out how arrays work in ARM assembly, but I am just overwhelmed. I want to initialize an array of size 20 to 0, 1, 2 and so on.
A[0] = 0
A[1] = 1
I can't even figure out how to print what I have to see if I did it correctly. This is what I have so far:
.data
.balign 4 # Memory location divisible by 4
string: .asciz "a[%d] = %d\n"
a: .skip 80 # allocates 20
.text
.global main
.extern printf
main:
push {ip, lr} # return address + dummy register
ldr r1, =a # set r1 to index point of array
mov r2, #0 # index r2 = 0
loop:
cmp r2, #20 # 20 elements?
beq end # Leave loop if 20 elements
add r3, r1, r2, LSL #2 # r3 = r1 + (r2*4)
str r2, [r3] # r3 = r2
add r2, r2, #1 # r2 = r2 + 1
b loop # branch to next loop iteration
print:
push {lr} # store return address
ldr r0, =string # format
bl printf # c printf
pop {pc} # return address
ARM confuses me enough as it is, I don't know what i'm doing wrong. If anyone could help me better understand how this works that would be much appreciated.
This might help down the line for others who want to know about how to allocate memory for array in arm assembly language
here is a simple example to add corresponding array elements and store in the third array.
.global _start
_start:
MOV R0, #5
LDR R1,=first_array # loading the address of first_array[0]
LDR R2,=second_array # loading the address of second_array[0]
LDR R7,=final_array # loading the address of final_array[0]
MOV R3,#5 # len of array
MOV R4,#0 # to store sum
check:
cmp R3,#1 # like condition in for loop for i>1
BNE loop # if R3 is not equal to 1 jump to the loop label
B _exit # else exit
loop:
LDR R5,[R1],#4 # loading the values and storing in registers and base register gets updated automatically R1 = R1 + 4
LDR R6,[R2],#4 # similarly
add R4,R5,R6
STR R4,[R7],#4 # storing the values back to the final array
SUB R3,R3,#1 # decrment value just like i-- in for loop
B check
_exit:
LDR R7,=final_array # before exiting checking the values stored
LDR R1, [R7] # R1 = 60
LDR R2, [R7,#4] # R2 = 80
LDR R3, [R7,#8] # R3 = 100
LDR R4, [R7,#12] # R4 = 120
MOV R7, #1 # terminate syscall, 1
SWI 0 # execute syscall
.data
first_array: .word 10,20,30,40
second_array: .word 50,60,70,80
final_array: .word 0,0,0,0,0
as mentioned your printf has problems, you can use the toolchain itself to see what the calling convention is, and then conform to that.
#include <stdio.h>
unsigned int a,b;
void notmain ( void )
{
printf("a[%d] = %d\n",a,b);
}
giving
00001008 <notmain>:
1008: e59f2010 ldr r2, [pc, #16] ; 1020 <notmain+0x18>
100c: e59f3010 ldr r3, [pc, #16] ; 1024 <notmain+0x1c>
1010: e5921000 ldr r1, [r2]
1014: e59f000c ldr r0, [pc, #12] ; 1028 <notmain+0x20>
1018: e5932000 ldr r2, [r3]
101c: eafffff8 b 1004 <printf>
1020: 0000903c andeq r9, r0, ip, lsr r0
1024: 00009038 andeq r9, r0, r8, lsr r0
1028: 0000102c andeq r1, r0, ip, lsr #32
Disassembly of section .rodata:
0000102c <.rodata>:
102c: 64255b61 strtvs r5, [r5], #-2913 ; 0xb61
1030: 203d205d eorscs r2, sp, sp, asr r0
1034: 000a6425 andeq r6, sl, r5, lsr #8
Disassembly of section .bss:
00009038 <b>:
9038: 00000000 andeq r0, r0, r0
0000903c <a>:
903c:
the calling convention is generally first parameter in r0, second in r1, third in r2 up to r3 then use the stack. There are many exceptions to this, but we can see here that the compiler which normally works fine with a printf call, wants the address of the format string in r0. the value of a then the value of b in r1 and r2 respectively.
Your printf has the string in r0, but a printf call with that format string needs three parameters.
The code above used a tail optimization and branch to printf rather than called it and returned from. The arm convention these days prefers the stack to be aligned on 64 bit boundaries, so you can put some register, you dont necessarily care to preserve on the push/pop in order to keep that alignment
push {r3,lr}
...
pop {r3,pc}
It certainly wont hurt you to do this, it may or may not hurt to not do it depending on what downstream assumes.
Your setup and loop should function just fine assuming that r1 (label a) is a word aligned address. Which it may or may not be if you mess with your string, should put a first then the string or put another alignment statement before a to insure the array is aligned. There are instruction set features that can simply the code, but it appears functional as is.

calculating the address of global offset table in arm literal pool

I am trying to understand the arm assembly code for writing the Literal Pool and Global OFFSET table
Compiling the C code with GNU ARM GCC
extern int i;
int foo(int j)
{
int t = i;
i = j;
return t;
}
GCC generates following code:
foo:
ldr r3, .L2
ldr r2, .L2+4
.LPIC0:
add r3, pc
ldr r3, [r3, r2]
# sp needed for prologue
ldr r2, [r3]
str r0, [r3]
mov r0, r2
bx lr
.L3:
.align 2
.L2:
.word _GLOBAL_OFFSET_TABLE_-(.LPIC0+4)
.word i(GOT)
I want to manually handle the global offset table in arm assembly.
Now I am facing difficulty to understand the above code.
Can any one please describe the literal pool calculation following lines of code?
.L2:
.word _GLOBAL_OFFSET_TABLE_-(.LPIC0+4)
.word i(GOT)
When compiled to a PIC(position independet code) file, global variable need to be relocated.
foo:
ldr r3, .L2
ldr r2, .L2+4
.LPIC0:
add r3, pc
ldr r3, [r3, r2]
Notice add r3, pc, in this instruction, pc is .LPIC0+4, so the result of add is _GLOBAL_OFFSET_TABLE_, which is the entry of the GOT.
.L2+4 is i(GOT), it is the offset of varaibel i in GOT.
Look at the result of objdump is more intuitive.
00000450 <foo>:
450: 4b03 ldr r3, [pc, #12] ; (460 <foo+0x10>)
452: 4a04 ldr r2, [pc, #16] ; (464 <foo+0x14>)
454: 447b add r3, pc
456: 589b ldr r3, [r3, r2]
458: 681a ldr r2, [r3, #0]
45a: 6018 str r0, [r3, #0]
45c: 4610 mov r0, r2
45e: 4770 bx lr
460: 00008ba8 andeq r8, r0, r8, lsr #23
464: 0000001c andeq r0, r0, ip, lsl r0
468: f3af 8000 nop.w
46c: f3af 8000 nop.w
In the disassembly, .L2 and .L2+4 is replaced with specific offset. the result of add r3, pc is 0x8ba8 + 0x458 = 0x9000. Then ldr r3, [r3, r2] would load from address 0x901c. Look up these address in the section header:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
...
[17] .got PROGBITS 00009000 001000 000024 04 WA 0 0 4
...
the address 0x9000 is the entry of global offset table, and 0x901c is also in this section. the symbol info of 0x901c could be find in the .rel.dyn section:
Relocation section '.rel.dyn' at offset 0x348 contains 7 entries:
Offset Info Type Sym.Value Sym. Name
...
00009018 00000415 R_ARM_GLOB_DAT 00000000 _Jv_RegisterClasses
0000901c 00000515 R_ARM_GLOB_DAT 00000000 i
00009020 00000615 R_ARM_GLOB_DAT 00000000 __cxa_finalize

inserting assembly code for arm into a C function, how do I insert extern variables?

I have a C-file f1.c with several functions inside. I need to write one of those functions in assembly code for armv4 and then put it back inside f1.c.
So I extracted the function I wanted in assembly code into another file (called test1.c) and compiled it with:
arm-linux-gnueabi-gcc -S -march=armv4 test1.c
In test1.c I have:
extern long int li1, li2, li3;
int main()
{
iowrite32(li1, li2);
iowrite32(li3, li1);
return 0;
}
After that I get the following code test1.s:
.arch armv4
.eabi_attribute 27, 3
.fpu vfpv3-d16
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 0
.eabi_attribute 18, 4
.file "test1.c"
.text
.align 2
.global main
.type main, %function
main:
# Function supports interworking.
# args = 0, pretend = 0, frame = 0
# frame_needed = 1, uses_anonymous_args = 0
stmfd sp!, {fp, lr}
add fp, sp, #4
ldr r3, .L2
ldr r2, [r3, #0]
ldr r3, .L2+4
ldr r3, [r3, #0]
mov r0, r2
mov r1, r3
bl iowrite32
ldr r3, .L2+8
ldr r2, [r3, #0]
ldr r3, .L2
ldr r3, [r3, #0]
mov r0, r2
mov r1, r3
bl iowrite32
mov r3, #0
mov r0, r3
sub sp, fp, #4
ldmfd sp!, {fp, lr}
bx lr
.L3:
.align 2
.L2:
.word li1
.word li2
.word li3
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
.section .note.GNU-stack,"",%progbits
The problem is that I need to put that assembly code (t1.s) back to file f1.C, so I use:
asm volatile( "stmfd sp!, {fp, lr}\n\t"
"add fp, sp, #4\n\t"
[...]);
BUT I don't know how I can put back instructions such as:
ldr r3, .L2
Because they refer to the extern variables, which are under the label L2 and f1.c won't compile if I try
"ldr r3, .L2\n\t"
Could anyone tell me how I can do it?
Thank you.
In the end, I'm doing it this way:
Instead of:
"ldr r3, .L2+4\n\t"
I'm saving the direction of the variable in another register:
"ldr r4, =li2\n\t"
And then I load it in the first one:
"ldr r3, [r4]\n\t"
I don't know if it is the correct answer, but it compiles and works as expected

This assembly code makes nothing

I'm working in a C file, which is something like this:
#define v2 0x560000a0
int main(void)
{
long int v1;
v1 = ioread32(v2);
return 0;
}
and I've extracted this part to write it in assembly:
int main ()
{
extern v1,v2;
v1=ioread32(v2);
return 0;
}
I'm trying to write the value of v2 in v1 using assembly code for armv4. Using
arm-linux-gnueabi-gcc -S -march=armv4 assembly_file.c
I get this code:
.arch armv4
.eabi_attribute 27, 3
.fpu vfpv3-d16
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 0
.eabi_attribute 18, 4
.file "txind-rsi.c"
.text
.align 2
.global main
.type main, %function
main:
# Function supports interworking.
# args = 0, pretend = 0, frame = 0
# frame_needed = 1, uses_anonymous_args = 0
stmfd sp!, {fp, lr}
add fp, sp, #4
ldr r3, .L2
ldr r3, [r3, #0]
mov r0, r3
bl ioread32
mov r2, r0
ldr r3, .L2+4
str r2, [r3, #0]
mov r3, #0
mov r0, r3
sub sp, fp, #4
ldmfd sp!, {fp, lr}
bx lr
.L3:
.align 2
.L2:
.word v1
.word v2
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
.section .note.GNU-stack,"",%progbits
I use that code to put it back inside the C file this way:
asm volatile(
"stmfd sp!, {fp, lr}\n"
"add fp, sp, #4\n"
"ldr r3, =v1\n"
"ldr r3, [r3, #0]\n"
"mov r0, r2\n"
"ldr r3, =v2\n"
"str r2, [r3, #0]\n"
"sub sp, fp, #4\n"
"ldmfd sp!, {fp, lr}\n"
"bx lr"
);
The code doesn't do anything.
In fact, it stops the target working.
Does anyones know why?
EDITED:
After reading your answers I have another question:
How would I put a constant value in a register?. The code in C would be this:
#define v2 0x560000a0
int main(void)
{
long int value = 0x0000ffff;
long int v1;
v1 = ioread32(v2);
iowrite32(v2,value);
return 0;
}
I've tried this:
asm volatile("mov r3, #value");
and I get an assembler message: "value symbol is in a different section";
I've also tried
asm volatile("mov r3, #0x0000ffff);
and the assembler message is:
"invalid constant(ffff) after fixup". And after reading this: Invalid constant after fixup?
I don't know how I can put that value into r3, as it seems I can't do it with mov.I'm using armv4, not armv7. And this solution proposed in the link, doesn't work for me:
asm volatile("ldr r3, =#0000ffff\n");
There are no problems with your code, bx lr is the proper way to terminate main, no issue there. Your code is most likely crashing due to the address you are accessing, it is probably not an address you are allowed to access...
#define v2 0x560000a0
int main(void)
{
long int v1;
v1 = ioread32(v2);
return 0;
}
if you optimize on the C compile step you can see a cleaner, simpler version
00000000 <main>:
0: e92d4008 push {r3, lr}
4: e59f0008 ldr r0, [pc, #8] ; 14 <main+0x14>
8: ebfffffe bl 0 <ioread32>
c: e3a00000 mov r0, #0
10: e8bd8008 pop {r3, pc}
14: 560000a0 strpl r0, [r0], -r0, lsr #1
which is not hard to implement in asm.
.globl main
main:
push {r3,lr}
ldr r0,=0x560000A0
bl ioread32
mov r0,#0
pop {r3,pc}
assembled and disassembled
00000000 <main>:
0: e92d4008 push {r3, lr}
4: e59f0008 ldr r0, [pc, #8] ; 14 <main+0x14>
8: ebfffffe bl 0 <ioread32>
c: e3a00000 mov r0, #0
10: e8bd8008 pop {r3, pc}
14: 560000a0 strpl r0, [r0], -r0, lsr #1
you had simplified the code to the point that v1 becomes dead code, but the function call cannot be optimized out, so the return is discarded.
if you dont use main but create a separate function that returns
#define v2 0x560000a0
long int fun(void)
{
long int v1;
v1 = ioread32(v2);
return v1;
}
...damn...tail optimization:
00000000 <fun>:
0: e59f0000 ldr r0, [pc] ; 8 <fun+0x8>
4: eafffffe b 0 <ioread32>
8: 560000a0 strpl r0, [r0], -r0, lsr #1
Oh well. You are on the right path, see what the C compiler generates then mimic or modify that. I suspect it is your ioread that is the problem and not the outer structure (why are you doing a 64 bit thing with a 32 bit read, maybe that is the problem long it is most likely going to be implemented as 64 bit).
The bx lr at the end would try to return from the current function. You probably don't want that. I guess that is also the reason for the crash, given that you don't let the compiler undo whatever setup it has done in the function prologue.
Also, in your asm block you don't use any locals so you don't need to adjust the stack pointer, and you don't need a new stack frame either. The mov r0, r2 is also broken because you have loaded the value into r3. You can delete the mov if you just load directly into whatever register you want. Furthermore, in gcc inline asm you should tell the compiler what registers you modify. Not doing so can also lead to a crash because you might overwrite values the compiler relies upon.
Not sure what's the point in doing this in assembly. If you insist on that, the following should work somewhat better:
asm volatile(
"ldr r3, =v1\n"
"ldr r2, [r3, #0]\n"
"ldr r3, =v2\n"
"str r2, [r3, #0]\n" ::: "r2", "r3"
);

Resources