I am trying to debug a precise bus error on a Arm cortex m4 chip.
The board is a teensy 3.1 with a freescale MK20DX256VLH7. The error only happens when i actually send characters with the uart and results in a forced hard fault because i dont have buserror and memory error handlers. The fault happens after a random amount of time between 1 second and 1 minute when sending 30 integers per second with the uart. Also when i print the values in binary, not the uartPutInt() but the uartPutBin() function, the fault does not happen.
I "hacked" the teensy so i can use SWD debugging.
I use a arm-none-eabi toolchain gcc, gdb.
I tried to double stack size but that doesn't help.
I tried different itoa() approaches, doesn't help.
full code can be accessed here:
https://github.com/paulusbrand/tricopter
The problem occurs in uartPutInt() function, its not the prettiest function but i tried different approaches and this one is the easiest to debug and understand.
original:
void uartPutInt(int32_t data) {
char buf[16] = {0};
uint32_t tmpData;
uint8_t neg = 0;
int8_t tmp = 0;
if(data<0) { // check negative
tmpData = -data;
neg=1;
}
else {
tmpData = data;
}
while(tmpData) { // convert to chars
uint8_t num = tmpData % 10;
buf[tmp++]=num+48;
tmpData/=10;
}
if(neg) { // add minus sign
buf[tmp++] = 45;
}
while(tmp>=0) {
uartPutChar(buf[tmp--]);
}
}
new version:
void uartPutInt(int32_t data) {
char buf[16] = {0};
uint32_t tmpData;
uint8_t neg = 0;
int8_t tmp = 0;
if(data<0) { // check negative
tmpData = -data;
neg=1;
}
else {
tmpData = data;
}
do { // convert te chars
uint8_t num = tmpData % 10;
buf[tmp++]=num+'0';
tmpData/=10;
} while(tmpData);
if(neg) { // add minus sign
buf[tmp++] = '-';
}
while(tmp>0) {
uartPutChar(buf[--tmp]);
}
}
When the bus error occurs i check the CFSR register in the SCB and find precise bus error and BFAR valid.
The value of BFAR and thus the problematic memory address is 0x01007fd2. Which is, as far as i can tell, in the code area of the memory but far beyond the end of the code. I not really sure what to do with this number.
The program counter PC when the error occurs is 0x1033. Which is in the uartPutInt() function. Disassembled below.
00000f6c <uartPutInt>:
f6c: b580 push {r7, lr}
f6e: b088 sub sp, #32
f70: af00 add r7, sp, #0
f72: 6078 str r0, [r7, #4]
f74: f107 0308 add.w r3, r7, #8
f78: 2200 movs r2, #0
f7a: 601a str r2, [r3, #0]
f7c: 3304 adds r3, #4
f7e: 2200 movs r2, #0
f80: 601a str r2, [r3, #0]
f82: 3304 adds r3, #4
f84: 2200 movs r2, #0
f86: 601a str r2, [r3, #0]
f88: 3304 adds r3, #4
f8a: 2200 movs r2, #0
f8c: 601a str r2, [r3, #0]
f8e: 3304 adds r3, #4
f90: 2300 movs r3, #0
f92: 76fb strb r3, [r7, #27]
f94: 687b ldr r3, [r7, #4]
f96: 2b00 cmp r3, #0
f98: da05 bge.n fa6 <uartPutInt+0x3a>
f9a: 687b ldr r3, [r7, #4]
f9c: 425b negs r3, r3
f9e: 61fb str r3, [r7, #28]
fa0: 2301 movs r3, #1
fa2: 76fb strb r3, [r7, #27]
fa4: e001 b.n faa <uartPutInt+0x3e>
fa6: 687b ldr r3, [r7, #4]
fa8: 61fb str r3, [r7, #28]
faa: e01f b.n fec <uartPutInt+0x80>
fac: 69f9 ldr r1, [r7, #28]
fae: 4b23 ldr r3, [pc, #140] ; (103c <uartPutInt+0xd0>)
fb0: fba3 2301 umull r2, r3, r3, r1
fb4: 08da lsrs r2, r3, #3
fb6: 4613 mov r3, r2
fb8: 009b lsls r3, r3, #2
fba: 4413 add r3, r2
fbc: 005b lsls r3, r3, #1
fbe: 1aca subs r2, r1, r3
fc0: 4613 mov r3, r2
fc2: 767b strb r3, [r7, #25]
fc4: 7eba ldrb r2, [r7, #26]
fc6: b2d3 uxtb r3, r2
fc8: 3301 adds r3, #1
fca: b2db uxtb r3, r3
fcc: 76bb strb r3, [r7, #26]
fce: b253 sxtb r3, r2
fd0: 7e7a ldrb r2, [r7, #25]
fd2: 3230 adds r2, #48 ; 0x30
fd4: b2d2 uxtb r2, r2
fd6: f107 0120 add.w r1, r7, #32
fda: 440b add r3, r1
fdc: f803 2c18 strb.w r2, [r3, #-24]
fe0: 69fb ldr r3, [r7, #28]
fe2: 4a16 ldr r2, [pc, #88] ; (103c <uartPutInt+0xd0>)
fe4: fba2 2303 umull r2, r3, r2, r3
fe8: 08db lsrs r3, r3, #3
fea: 61fb str r3, [r7, #28]
fec: 69fb ldr r3, [r7, #28]
fee: 2b00 cmp r3, #0
ff0: d1dc bne.n fac <uartPutInt+0x40>
ff2: 7efb ldrb r3, [r7, #27]
ff4: 2b00 cmp r3, #0
ff6: d00b beq.n 1010 <uartPutInt+0xa4>
ff8: 7eba ldrb r2, [r7, #26]
ffa: b2d3 uxtb r3, r2
ffc: 3301 adds r3, #1
ffe: b2db uxtb r3, r3
1000: 76bb strb r3, [r7, #26]
1002: b253 sxtb r3, r2
1004: f107 0220 add.w r2, r7, #32
1008: 4413 add r3, r2
100a: 222d movs r2, #45 ; 0x2d
100c: f803 2c18 strb.w r2, [r3, #-24]
1010: e00d b.n 102e <uartPutInt+0xc2>
1012: 7eba ldrb r2, [r7, #26]
1014: b2d3 uxtb r3, r2
1016: 3b01 subs r3, #1
1018: b2db uxtb r3, r3
101a: 76bb strb r3, [r7, #26]
101c: b253 sxtb r3, r2
101e: f107 0220 add.w r2, r7, #32
1022: 4413 add r3, r2
1024: f813 3c18 ldrb.w r3, [r3, #-24]
1028: 4618 mov r0, r3
102a: f7ff ff87 bl f3c <uartPutChar>
102e: f997 301a ldrsb.w r3, [r7, #26]
1032: 2b00 cmp r3, #0
1034: daed bge.n 1012 <uartPutInt+0xa6>
1036: 3720 adds r7, #32
1038: 46bd mov sp, r7
103a: bd80 pop {r7, pc}
103c: cccccccd stclgt 12, cr12, [ip], {205} ; 0xcd
Can somebody please help me with this?
Thanks!
tmp is uninitialized:
void uartPutInt(int32_t data) {
char buf[16] = {0};
uint32_t tmpData;
uint8_t neg = 0;
int8_t tmp; // not initialized
if(data<0) { // check negative
tmpData = -data;
neg=1;
}
else {
tmpData = data;
}
while(tmpData) { // convert to chars
uint8_t num = tmpData % 10;
// what's in tmp right now?!?!
buf[tmp++]=num+48;
tmpData/=10;
}
if(neg) { // add minus sign
buf[tmp++] = 45;
}
while(tmp>=0) {
uartPutChar(buf[tmp--]);
}
}
There are many topics here whether a direct access or an indirect access (via pointer) are faster when accessing structure members, in C.
One example: C pointers vs direct member access for structs
The general opinion is direct access will be faster (at least theoretically) since pointer dereferencing is not used.
So I gave it a try with a chunk of code in my system: GNU Embedded Tools GCC 4.7.4, generating code for ARM (actually ARM-Cortex-A15).
Surprisingly, direct access was much slower. Then I generated the assembly codes for the object file.
Direct access code has 114 lines of assembly code, and indirect access code has 33 lines of assembly code. What is going on here?
Below are the C code and generated assembly code of the functions. The structures are all map to external memory and the structure members are all one-byte word long (unsigned char type).
First function, with indirect access:
void sub_func_1(unsigned int num_of, struct file_s *__restrict__ first_file_ptr, struct file_s *__restrict__ second_file_ptr, struct output_s *__restrict__ output_ptr)
{
if(LIKELY(num_of == 0))
{
output_ptr->curr_id = UNUSED;
output_ptr->curr_cnt = output_ptr->cnt;
output_ptr->curr_mode = output_ptr->_mode;
output_ptr->curr_type = output_ptr->type;
output_ptr->curr_size = output_ptr->size;
output_ptr->curr_allocation_type = output_ptr->allocation_type;
output_ptr->curr_allocation_localized = output_ptr->allocation_localized;
output_ptr->curr_mode_enable = output_ptr->mode_enable;
if(output_ptr->curr_cnt == 1)
{
first_file_ptr->status = BLOCK_IDLE;
first_file_ptr->type = USER_DATA_TYPE;
first_file_ptr->index = FIRST__WORD;
first_file_ptr->layer_cnt = output_ptr->layer_cnt;
second_file_ptr->status = DISABLED;
second_file_ptr->index = 0;
second_file_ptr->redundancy_version = 1;
output_ptr->total_layer_cnt = first_file_ptr->layer_cnt;
}
}
}
00000000 <sub_func_1>:
0: e3500000 cmp r0, #0
4: e92d01f0 push {r4, r5, r6, r7, r8}
8: 1a00001b bne 7c <sub_func_1+0x7c>
c: e5d34007 ldrb r4, [r3, #7]
10: e3a05008 mov r5, #8
14: e5d3c003 ldrb ip, [r3, #3]
18: e5d38014 ldrb r8, [r3, #20]
1c: e5c35001 strb r5, [r3, #1]
20: e5d37015 ldrb r7, [r3, #21]
24: e5d36018 ldrb r6, [r3, #24]
28: e5c34008 strb r4, [r3, #8]
2c: e5d35019 ldrb r5, [r3, #25]
30: e35c0001 cmp ip, #1
34: e5c3c005 strb ip, [r3, #5]
38: e5d34012 ldrb r4, [r3, #18]
3c: e5c38010 strb r8, [r3, #16]
40: e5c37011 strb r7, [r3, #17]
44: e5c3601a strb r6, [r3, #26]
48: e5c3501b strb r5, [r3, #27]
4c: e5c34013 strb r4, [r3, #19]
50: 1a000009 bne 7c <sub_func_1+0x7c>
54: e5d3400b ldrb r4, [r3, #11]
58: e3a05005 mov r5, #5
5c: e5c1c000 strb ip, [r1]
60: e5c10002 strb r0, [r1, #2]
64: e5c15001 strb r5, [r1, #1]
68: e5c20000 strb r0, [r2]
6c: e5c14003 strb r4, [r1, #3]
70: e5c20005 strb r0, [r2, #5]
74: e5c2c014 strb ip, [r2, #20]
78: e5c3400f strb r4, [r3, #15]
7c: e8bd01f0 pop {r4, r5, r6, r7, r8}
80: e12fff1e bx lr
Second function, with direct access:
void sub_func_2(unsigned int output_index, unsigned int cc_index, unsigned int num_of)
{
if(LIKELY(num_of == 0))
{
output_file[output_index].curr_id = UNUSED;
output_file[output_index].curr_cnt = output_file[output_index].cnt;
output_file[output_index].curr_mode = output_file[output_index]._mode;
output_file[output_index].curr_type = output_file[output_index].type;
output_file[output_index].curr_size = output_file[output_index].size;
output_file[output_index].curr_allocation_type = output_file[output_index].allocation_type;
output_file[output_index].curr_allocation_localized = output_file[output_index].allocation_localized;
output_file[output_index].curr_mode_enable = output_file[output_index].mode_enable;
if(output_file[output_index].curr_cnt == 1)
{
output_file[output_index].cc_file[cc_index].file[0].status = BLOCK_IDLE;
output_file[output_index].cc_file[cc_index].file[0].type = USER_DATA_TYPE;
output_file[output_index].cc_file[cc_index].file[0].index = FIRST__WORD;
output_file[output_index].cc_file[cc_index].file[0].layer_cnt = output_file[output_index].layer_cnt;
output_file[output_index].cc_file[cc_index].file[1].status = DISABLED;
output_file[output_index].cc_file[cc_index].file[1].index = 0;
output_file[output_index].cc_file[cc_index].file[1].redundancy_version = 1;
output_file[output_index].total_layer_cnt = output_file[output_index].cc_file[cc_index].file[0].layer_cnt;
}
}
}
00000084 <sub_func_2>:
84: e92d0ff0 push {r4, r5, r6, r7, r8, r9, sl, fp}
88: e3520000 cmp r2, #0
8c: e24dd018 sub sp, sp, #24
90: e58d2004 str r2, [sp, #4]
94: 1a000069 bne 240 <sub_func_2+0x1bc>
98: e3a03d61 mov r3, #6208 ; 0x1840
9c: e30dc0c0 movw ip, #53440 ; 0xd0c0
a0: e340c001 movt ip, #1
a4: e3002000 movw r2, #0
a8: e0010193 mul r1, r3, r1
ac: e3402000 movt r2, #0
b0: e3067490 movw r7, #25744 ; 0x6490
b4: e3068488 movw r8, #25736 ; 0x6488
b8: e3a0b008 mov fp, #8
bc: e3066498 movw r6, #25752 ; 0x6498
c0: e02c109c mla ip, ip, r0, r1
c4: e082c00c add ip, r2, ip
c8: e28c3b19 add r3, ip, #25600 ; 0x6400
cc: e08c4007 add r4, ip, r7
d0: e5d39083 ldrb r9, [r3, #131] ; 0x83
d4: e08c5006 add r5, ip, r6
d8: e5d3a087 ldrb sl, [r3, #135] ; 0x87
dc: e5c3b081 strb fp, [r3, #129] ; 0x81
e0: e5c39085 strb r9, [r3, #133] ; 0x85
e4: e2833080 add r3, r3, #128 ; 0x80
e8: e7cca008 strb sl, [ip, r8]
ec: e5d4a004 ldrb sl, [r4, #4]
f0: e7cca007 strb sl, [ip, r7]
f4: e5d47005 ldrb r7, [r4, #5]
f8: e5c47001 strb r7, [r4, #1]
fc: e7dc6006 ldrb r6, [ip, r6]
100: e5d5c001 ldrb ip, [r5, #1]
104: e5c56002 strb r6, [r5, #2]
108: e5c5c003 strb ip, [r5, #3]
10c: e5d4c002 ldrb ip, [r4, #2]
110: e5c4c003 strb ip, [r4, #3]
114: e5d33005 ldrb r3, [r3, #5]
118: e3530001 cmp r3, #1
11c: 1a000047 bne 240 <sub_func_2+0x1bc>
120: e30dc0c0 movw ip, #53440 ; 0xd0c0
124: e30db0c0 movw fp, #53440 ; 0xd0c0
128: e1a0700c mov r7, ip
12c: e7dfc813 bfi ip, r3, #16, #16
130: e1a05007 mov r5, r7
134: e1a0900b mov r9, fp
138: e02c109c mla ip, ip, r0, r1
13c: e1a04005 mov r4, r5
140: e1a0a00b mov sl, fp
144: e7df9813 bfi r9, r3, #16, #16
148: e7dfb813 bfi fp, r3, #16, #16
14c: e1a06007 mov r6, r7
150: e7dfa813 bfi sl, r3, #16, #16
154: e58dc008 str ip, [sp, #8]
158: e7df6813 bfi r6, r3, #16, #16
15c: e1a0c004 mov ip, r4
160: e7df4813 bfi r4, r3, #16, #16
164: e02b109b mla fp, fp, r0, r1
168: e7df5813 bfi r5, r3, #16, #16
16c: e0291099 mla r9, r9, r0, r1
170: e7df7813 bfi r7, r3, #16, #16
174: e7dfc813 bfi ip, r3, #16, #16
178: e0261096 mla r6, r6, r0, r1
17c: e0241094 mla r4, r4, r0, r1
180: e082b00b add fp, r2, fp
184: e0829009 add r9, r2, r9
188: e02a109a mla sl, sl, r0, r1
18c: e28bbc65 add fp, fp, #25856 ; 0x6500
190: e58d600c str r6, [sp, #12]
194: e2899c65 add r9, r9, #25856 ; 0x6500
198: e3a06005 mov r6, #5
19c: e58d4010 str r4, [sp, #16]
1a0: e59d4008 ldr r4, [sp, #8]
1a4: e0251095 mla r5, r5, r0, r1
1a8: e5cb3000 strb r3, [fp]
1ac: e082a00a add sl, r2, sl
1b0: e59db00c ldr fp, [sp, #12]
1b4: e5c96001 strb r6, [r9, #1]
1b8: e59d6004 ldr r6, [sp, #4]
1bc: e28aac65 add sl, sl, #25856 ; 0x6500
1c0: e58d5014 str r5, [sp, #20]
1c4: e0271097 mla r7, r7, r0, r1
1c8: e0825004 add r5, r2, r4
1cc: e30d40c0 movw r4, #53440 ; 0xd0c0
1d0: e02c109c mla ip, ip, r0, r1
1d4: e0855008 add r5, r5, r8
1d8: e7df4813 bfi r4, r3, #16, #16
1dc: e5ca6002 strb r6, [sl, #2]
1e0: e5d59003 ldrb r9, [r5, #3]
1e4: e082600b add r6, r2, fp
1e8: e59db014 ldr fp, [sp, #20]
1ec: e0201094 mla r0, r4, r0, r1
1f0: e2866c65 add r6, r6, #25856 ; 0x6500
1f4: e59d1010 ldr r1, [sp, #16]
1f8: e306a53c movw sl, #25916 ; 0x653c
1fc: e0827007 add r7, r2, r7
200: e2877c65 add r7, r7, #25856 ; 0x6500
204: e082c00c add ip, r2, ip
208: e5c69003 strb r9, [r6, #3]
20c: e59d6004 ldr r6, [sp, #4]
210: e28ccc65 add ip, ip, #25856 ; 0x6500
214: e082500b add r5, r2, fp
218: e0820000 add r0, r2, r0
21c: e0824001 add r4, r2, r1
220: e085500a add r5, r5, sl
224: e0808008 add r8, r0, r8
228: e7c4600a strb r6, [r4, sl]
22c: e5c56005 strb r6, [r5, #5]
230: e5c73050 strb r3, [r7, #80] ; 0x50
234: e5dc3003 ldrb r3, [ip, #3]
238: e287704c add r7, r7, #76 ; 0x4c
23c: e5c83007 strb r3, [r8, #7]
240: e28dd018 add sp, sp, #24
244: e8bd0ff0 pop {r4, r5, r6, r7, r8, r9, sl, fp}
248: e12fff1e bx lr
And last part, my compile options are:
# Compile options.
C_OPTS = -Wall \
-std=gnu99 \
-fgnu89-inline \
-Wcast-align \
-Werror=uninitialized \
-Werror=maybe-uninitialized \
-Werror=overflow \
-mcpu=cortex-a15 \
-mtune=cortex-a15 \
-mabi=aapcs \
-mfpu=neon \
-ftree-vectorize \
-ftree-slp-vectorize \
-ftree-vectorizer-verbose=4 \
-mfloat-abi=hard \
-O3 \
-flto \
-marm \
-ffat-lto-objects \
-fno-gcse \
-fno-strict-aliasing \
-fno-delete-null-pointer-checks \
-fno-strict-overflow \
-fuse-linker-plugin \
-falign-functions=4 \
-falign-loops=4 \
-falign-labels=4 \
-falign-jumps=4
Update:
Note: I deleted the structure definitions because there was differences with the version of my own program. It is actually a huge structure and it is not efficient to put here completely.
As suggested, I get rid of -fno-gcse, and the generated asm is not huge as before.
Without -fno-gcse, sub_func_1 generates the same code as above.
For sub_func_2:
00000084 <sub_func_2>:
84: e3520000 cmp r2, #0
88: e92d0070 push {r4, r5, r6}
8c: 1a000030 bne 154 <sub_func_2+0xd0>
90: e30d30c0 movw r3, #53440 ; 0xd0c0
94: e3a06008 mov r6, #8
98: e3403001 movt r3, #1
9c: e0030093 mul r3, r3, r0
a0: e3a00d61 mov r0, #6208 ; 0x1840
a4: e0213190 mla r1, r0, r1, r3
a8: e59f30ac ldr r3, [pc, #172] ; 15c <sub_func_2+0xd8>
ac: e0831001 add r1, r3, r1
b0: e2813b19 add r3, r1, #25600 ; 0x6400
b4: e5d34083 ldrb r4, [r3, #131] ; 0x83
b8: e1a00003 mov r0, r3
bc: e5d35087 ldrb r5, [r3, #135] ; 0x87
c0: e5c36081 strb r6, [r3, #129] ; 0x81
c4: e5c34085 strb r4, [r3, #133] ; 0x85
c8: e3064488 movw r4, #25736 ; 0x6488
cc: e2833080 add r3, r3, #128 ; 0x80
d0: e7c15004 strb r5, [r1, r4]
d4: e5d05094 ldrb r5, [r0, #148] ; 0x94
d8: e0844006 add r4, r4, r6
dc: e7c15004 strb r5, [r1, r4]
e0: e5d04095 ldrb r4, [r0, #149] ; 0x95
e4: e5d0c092 ldrb ip, [r0, #146] ; 0x92
e8: e5c04091 strb r4, [r0, #145] ; 0x91
ec: e3064498 movw r4, #25752 ; 0x6498
f0: e7d15004 ldrb r5, [r1, r4]
f4: e5c0c093 strb ip, [r0, #147] ; 0x93
f8: e5d04099 ldrb r4, [r0, #153] ; 0x99
fc: e5c0509a strb r5, [r0, #154] ; 0x9a
100: e5c0409b strb r4, [r0, #155] ; 0x9b
104: e5d33005 ldrb r3, [r3, #5]
108: e3530001 cmp r3, #1
10c: 1a000010 bne 154 <sub_func_2+0xd0>
110: e281cc65 add ip, r1, #25856 ; 0x6500
114: e3a06005 mov r6, #5
118: e2810b19 add r0, r1, #25600 ; 0x6400
11c: e1a0500c mov r5, ip
120: e5cc3000 strb r3, [ip]
124: e1a0400c mov r4, ip
128: e5cc6001 strb r6, [ip, #1]
12c: e5cc2002 strb r2, [ip, #2]
130: e5d0608b ldrb r6, [r0, #139] ; 0x8b
134: e5cc6003 strb r6, [ip, #3]
138: e306c53c movw ip, #25916 ; 0x653c
13c: e7c1200c strb r2, [r1, ip]
140: e5c52041 strb r2, [r5, #65] ; 0x41
144: e285503c add r5, r5, #60 ; 0x3c
148: e5c43050 strb r3, [r4, #80] ; 0x50
14c: e284404c add r4, r4, #76 ; 0x4c
150: e5c0608f strb r6, [r0, #143] ; 0x8f
154: e8bd0070 pop {r4, r5, r6}
158: e12fff1e bx lr
15c: 00000000 .word 0x00000000
TL:DR: can't reproduce that insane compiler output. Maybe the surrounding code + LTO did it?
I do have suggestions to improve the code: see the stuff below about copying whole structs instead of copying many individual members.
The question you linked is about accessing a value-type global directly vs. through a global pointer. On ARM, where it takes multiple instructions or a load from a nearby constant to get an arbitrary 32bit pointer into a register, passing around pointers is better than having each function reference a global directly.
See this example on the Godbolt Compiler Explorer (ARM gcc 4.8.2 -O3)
struct example {
int a, b, c;
} global_example;
int load_global(void) { return global_example.c; }
movw r3, #:lower16:global_example # tmp113,
movt r3, #:upper16:global_example # tmp113,
ldr r0, [r3, #8] #, global_example.c
bx lr #
int load_pointer(struct example *p) { return p->c; }
ldr r0, [r0, #8] #, p_2(D)->c
bx lr #
(Apparently gcc is horrible at passing structs by val as function args, see the code for byval(struct example by_val) on the godbolt link.)
Even worse is if you have a global pointer: first you have to load the value of the pointer, then another load to dereference it. This is the indirection overhead that was being discussed in the question you linked. If both loads miss in cache, you're paying the round-trip latency twice. The load address for the 2nd load isn't available until the first load completes, so no pipelining of those memory requests is possible even on an out-of-order CPU.
If you already have a pointer as an arg, it will be in a register. Dereferencing it is the same as loading from a global. (But better, because you don't need to get the global's address into a register yourself.)
Your real code
I can't reproduce your massive asm output with ARM gcc 4.8.2 on Godbolt, or locally with ARM gcc 5.2.1. I'm not using LTO, though, since I don't have a complete test program.
All I can see is just slightly larger code to do some index math.
bfi is Bitfield Insert. I think 144: e7df9813 bfi r9, r3, #16, #16 is setting the top half of r9 = low half of r3. I don't see how that and mla (integer mul-accumulate) make much sense. Other than perverse results from -ftree-vectorize, all I can think of is maybe -fno-gcse has a really bad impact for the version of gcc you tested.
Is it manipulating constants that are going to be stored? The code you actually posted #defines everything to 0, which gcc takes advantage of. (It also takes advantage of the fact that it already has 1 in a register if curr_cnt == 1, and stores that register for the second_file_ptr->redundancy_version = 1;). ARM doesn't have a str [mem], immediate or anything like x86's mov [mem], imm.
If your compiler output is from code with different values for those constants, the compiler would be doing more work to store different things.
Unfortunately gcc is bad at merging narrow stores into a single wider store (long-standing missed-optimization bug). For x86, clang does this in at least one case, storing 0x0100 (256) instead of a 0 and a 1. (check on godbolt by flipping the compiler to clang 3.7.1 or something, and removing the ARM-specific compiler args. There's a mov word ptr \[rsi\], 256 where gcc uses
mov BYTE PTR [rsi], 0 # *first_file_ptr_23(D).status,
mov BYTE PTR [rsi+1], 1 # *first_file_ptr_23(D).type,
If you arranged your structs carefully, there would be more opportunities for copying 4B blocks in this function.
It might also help to have two identical sub-structs of curr and not-curr, instead of curr_size and size. You might have to declare it packed to avoid padding after the sub-structs, though. Your two groups of members aren't in exactly the same order, which prevents compilers from doing much block-copying anyway when you do a bunch of assignments.
It helps gcc and clang copy multiple bytes at once if you do:
struct output_s_optimized {
struct __attribute__((packed)) stuff {
unsigned char cnt,
mode,
type,
size,
allocation_type,
allocation_localized,
mode_enable;
} curr; // 7B
unsigned char curr_id; // no non-curr id?
struct stuff non_curr;
unsigned char layer_cnt;
// Another 8 byte boundary here
unsigned char total_layer_cnt;
struct cc_file_s cc_file[128];
};
void foo(struct output_s_optimized *p) {
p->curr_id = 0;
p->non_curr = p->curr;
}
void bar(struct output_s_optimized *output_ptr) {
output_ptr->curr_id = 0;
output_ptr->curr.cnt = output_ptr->non_curr.cnt;
output_ptr->curr.mode = output_ptr->non_curr.mode;
output_ptr->curr.type = output_ptr->non_curr.type;
output_ptr->curr.size = output_ptr->non_curr.size;
output_ptr->curr.allocation_type = output_ptr->non_curr.allocation_type;
output_ptr->curr.allocation_localized = output_ptr->non_curr.allocation_localized;
output_ptr->curr.mode_enable = output_ptr->non_curr.mode_enable;
}
gcc 4.8.2 compiles foo() to three copies: byte, 2B, and 4B, even on ARM. It compiles bar() to eight 1B copies, and so does clang-3.8 on x86. So copying whole structs can help your compiler a lot (as well as making sure the data to be copied is arranged in the same order in both locations).
the same code on x86: nothing new
You can use -fverbose-asm to put comments on each line. For x86, the compiler output from gcc 6.1 -O3 is very similar between versions, as you can see on the Godbolt Compiler Explorer. x86 addressing modes can index a global variable directly, so you see stuff like
movzx edi, BYTE PTR [rcx+10] # *output_ptr_7(D)._mode
# where rcx is the output_ptr arg, used directly
vs.
movzx ecx, BYTE PTR output_file[rdi+10] # output_file[output_index_7(D)]._mode
# where rdi = output_index * 1297 (sizeof(output_file[0])), calculated once at the start
(gcc apparently doesn't care that each instruction has a 4B displacement as part of the addressing mode, but this is an ARM question so I won't go tradeoffs between code-size and insn count with x86's variable-length insns.)
In broad (architecture-agnostic) terms, this is what your instructions do:
global_structure_pointer->field = value;
loads the value of global_structure_pointer into an addressing register.
adds the offset represented by field to the addressing register.
stores value into the memory location addressed by the addressing register.
global_structure[index].field = value;
loads the address of global_structure into an addressing register.
loads the value of index into an arithmetic register.
multiplies the arithmetic register by the size of global_structure.
adds the arithmetic register to the addressing register.
stores value into the memory location addressed by the addressing register.
Your confusion seems to be due to a misunderstanding of what "direct access" actually is.
THIS is direct access:
global_structure.field = value;
What you thought of as direct access is in fact indexed access.
I have some given code that uses variable length arrays on the stack in C. I cannot easily change that code to use malloced buffers on the heap, since I am working on some stuff where I have no operating system support for dynamic memory. However when I test my method the stack actually get's smashed instead (i.e. the SP get's set to a completely bogus address). To figure out what is going on I had a look at the assembly and I was completely confused by the compiler output.
I am running the code on a pandaboard ES (OMAP4460 ARM processor).
In order to test how variable size arrays on the stack are actually compiled, I produced a simple working test program. However again the code was much too confusing for me to understand.
Here is what I do not understand:
When I compile a simple function:
int test_method(size_t i)
{
unsigned char buffer[10];
return -1;
}
I get some very simple assembler code:
80003a68 <test_method>:
80003a68: b480 push {r7} ; store frame pointer
80003a6a: b087 sub sp, #28 ; make stack frame (size 28)
80003a6c: af00 add r7, sp, #0 ; set frame pointer
80003a6e: 6078 str r0, [r7, #4]; store parameter on stack
80003a70: f04f 0300 mov.w r3, #0 ; load return value
80003a74: 4618 mov r0, r3 ; put return value in return register
80003a76: f107 071c add.w r7, r7, #28 ; destroy stack frame
80003a7a: 46bd mov sp, r7 ; ...
80003a7c: bc80 pop {r7} ; pop stored frame pointer
80003a7e: 4770 bx lr ; return
But when I try this with a variable size array using the following code:
int test_method(size_t i)
{
unsigned char buffer[i];
return 0;
}
I get this assembly:
80003a68 <test_method>:
80003a68: e92d 03f0 stmdb sp!, {r4, r5, r6, r7, r8, r9}
80003a6c: b084 sub sp, #16
80003a6e: af00 add r7, sp, #0
80003a70: 6078 str r0, [r7, #4]
80003a72: 4669 mov r1, sp
80003a74: 460e mov r6, r1
80003a76: f8d7 c004 ldr.w ip, [r7, #4]
80003a7a: 4661 mov r1, ip
80003a7c: f101 31ff add.w r1, r1, #4294967295 ; 0xffffffff
80003a80: 60b9 str r1, [r7, #8]
80003a82: 4660 mov r0, ip
80003a84: f04f 0100 mov.w r1, #0
80003a88: f04f 38ff mov.w r8, #4294967295 ; 0xffffffff
80003a8c: f04f 090f mov.w r9, #15
80003a90: ea00 0008 and.w r0, r0, r8
80003a94: ea01 0109 and.w r1, r1, r9
80003a98: ea4f 7850 mov.w r8, r0, lsr #29
80003a9c: ea4f 05c1 mov.w r5, r1, lsl #3
80003aa0: ea48 0505 orr.w r5, r8, r5
80003aa4: ea4f 04c0 mov.w r4, r0, lsl #3
80003aa8: f04f 30ff mov.w r0, #4294967295 ; 0xffffffff
80003aac: f04f 010f mov.w r1, #15
80003ab0: ea04 0400 and.w r4, r4, r0
80003ab4: ea05 0501 and.w r5, r5, r1
80003ab8: 4660 mov r0, ip
80003aba: f04f 0100 mov.w r1, #0
80003abe: f04f 34ff mov.w r4, #4294967295 ; 0xffffffff
80003ac2: f04f 050f mov.w r5, #15
80003ac6: ea00 0004 and.w r0, r0, r4
80003aca: ea01 0105 and.w r1, r1, r5
80003ace: ea4f 7450 mov.w r4, r0, lsr #29
80003ad2: ea4f 03c1 mov.w r3, r1, lsl #3
80003ad6: ea44 0303 orr.w r3, r4, r3
80003ada: ea4f 02c0 mov.w r2, r0, lsl #3
80003ade: f04f 30ff mov.w r0, #4294967295 ; 0xffffffff
80003ae2: f04f 010f mov.w r1, #15
80003ae6: ea02 0200 and.w r2, r2, r0
80003aea: ea03 0301 and.w r3, r3, r1
80003aea: ea03 0301 and.w r3, r3, r1
80003aee: 4663 mov r3, ip
80003af0: f103 0307 add.w r3, r3, #7
80003af4: f103 0307 add.w r3, r3, #7
80003af8: ea4f 03d3 mov.w r3, r3, lsr #3
80003afc: ea4f 03c3 mov.w r3, r3, lsl #3
80003b00: ebad 0d03 sub.w sp, sp, r3
80003b04: 466b mov r3, sp
80003b06: f103 0307 add.w r3, r3, #7
80003b0a: ea4f 03d3 mov.w r3, r3, lsr #3
80003b0e: ea4f 03c3 mov.w r3, r3, lsl #3
80003b12: 60fb str r3, [r7, #12]
80003b14: f04f 0300 mov.w r3, #0
80003b18: 46b5 mov sp, r6
80003b1a: 4618 mov r0, r3
80003b1c: f107 0710 add.w r7, r7, #16
80003b20: 46bd mov sp, r7
80003b22: e8bd 03f0 ldmia.w sp!, {r4, r5, r6, r7, r8, r9}
80003b26: 4770 bx lr
Where does all this additional logic come frome? I would have thought, that it would have been enough to just enlarge the stack frame by the size i (passed in r0), maybe with an addition of some extra code to keep the alignment. But why are all these additional registers written with 0xffffffff and #15. I cannot really make any sense of this assembly the compiler is giving me.
This is not really an answer just collecting more info that doesnt fit in a comment
typedef unsigned int size_t;
int test_method1(size_t i)
{
unsigned char buffer[10];
return -1;
}
int test_method2(size_t i)
{
unsigned char buffer[i];
return 0;
}
arm-none-eabi-gcc -O2 -c tm1.c -o tm1.o
arm-none-eabi-objdump -D tm1.o
basically optimizes everything away
00000000 <test_method1>:
0: e3e00000 mvn r0, #0
4: e12fff1e bx lr
00000008 <test_method2>:
8: e3a00000 mov r0, #0
c: e12fff1e bx lr
although bad code this should get the compiler to not optimize everything away
typedef unsigned int size_t;
unsigned char *test_method1(size_t i, size_t j)
{
unsigned char buffer[10];
return(&buffer[j]);
}
unsigned char *test_method2(size_t i, size_t j)
{
unsigned char buffer[i];
return(&buffer[j]);
}
00000000 <test_method1>:
0: e24dd010 sub sp, sp, #16
4: e28d3004 add r3, sp, #4
8: e0830001 add r0, r3, r1
c: e28dd010 add sp, sp, #16
10: e12fff1e bx lr
00000014 <test_method2>:
14: e92d0808 push {r3, fp}
18: e2800007 add r0, r0, #7
1c: e3c00007 bic r0, r0, #7
20: e28db004 add fp, sp, #4
24: e04dd000 sub sp, sp, r0
28: e08d0001 add r0, sp, r1
2c: e24bd004 sub sp, fp, #4
30: e8bd0808 pop {r3, fp}
34: e12fff1e bx lr
And it did and I think it perhaps answered your question. How the compiler handles arrays that are variable length is that in this case it did math on the stack pointer in the amount of the size of the array, basically allocating the dynamic array on the stack as you would expect. For the static sized array the math done on the stack was a static number not a passed in parameter.