To illustrate my problem, I just implemented demonstration code:
#include <stdint.h> uint64_t read(const uint32_t *wordStream) { uint32_t low = *wordStream++; uint64_t high = *wordStream++; high <<= 32; return high | low; }
This code reads (according to C) two unsigned 32 bit words. It assumes a little endian notation. Then it assembles these two words into a 64 bit value and returns it.
Unfortunately, the compiler assumes, that the first word is 64 bit aligned (by using the LDRD instruction which is only allowed on aligned 64 bit values):
; generated by Component: ARM Compiler 5.06 update 1 (build 61) Tool: armcc [4d35ad] ; commandline armcc [--thumb -c --asm -otestCode.o --cpu=SC300 testCode.c] THUMB REQUIRE8 PRESERVE8 AREA ||.text||, CODE, READONLY, ALIGN=1 read PROC LDRD r2,r1,[r0,#0] MOV r0,r2 BX lr ENDP ...
Where did I tell the compiler that the first word is aligned?
I believe for this processor family (SC300) the LDRD doess require 32-bit alignment, but not 64-bit. So only if it is not 32-bit aligned should it cause any problem on your processor. The way you specified the pointer coming into the function DOES specify that the compiler may assume that it is 32-bit aligned. As long as it is 32-bit aligned you should be fine. If it is not 32-bit aligned it will fault. IF the pointer may ever not be 32-bit aligned, you will want to specify that there is no guarantee that the pointer is 32-bit aligned. In this case the compiler will generate code that will not fault.
The LDRD has bitten once in recent times, where the compiler was loading a double, and the byte packed records would cause it to fault. It was an easily identifiable issue for anyone familiar with the old ARM7/9 parts, and remedied with an memcpy(), but it might frustrate the average PC coder porting "working" code.
double dx = *((double *)&p[8]); // where p is a byte pointer, alignment hazard
#include <stdint.h> #include <string.h> uint64_t read(const void *wordStream) { // alignment agnostic uint64_t u64; memcpy(&u64, wordStream, sizeof(uint64_t)); return u64; }
#include <stdint.h> uint64_t read(const void *wordStream) { // alignment agnostic return (*(__packed uint64_t *)wordStream); }
You may be right about the should part. It is not a compiler error. I didn't find any hint in the ARM or TRM about the necessity of 64 bit alignment for LDRD access. Unfortunately, there are processors in this family raising a security alert in this case.
I actually saw it (using an address ending on binary 100). Also, the derivates user manual didn't show any alignment requirement.
"In this case the compiler will generate code that will not fault." simply is wrong on my derivate. I'm sure that the code runs fine on almost all other derivates.
Still, I would like to have a solution for that derivate. If ARM/Keil doesn't want to provide it because most users don't need it, that's ok. There are software workarounds. But those are inconvenient.
A personal remark: You may have noticed that I used code different from the code usually posted along with problems of this kind. In contrast to those other postings, my code is formally correct. (No illicit casts.) Therefore I found your captialisation of "DOES" unnecessary and insulting.
Thanks for taking the time to think about my problem anyway.
Werner
Thank you for this suggestion.
It definitely works. It also produces a lot of unnecessary (performance) overhead. I just didn't see the need to call a function if (in C) there is a fine way to express the access.
Of course, my real field of application is a lot more complex. Instead of calling memcpy, I will implement or embed the read function in assembly language.
Thanks again, Werner
I'm sorry, I read your post wrong. You weren't suggesting that the compiler produces correct code in my case, but suggested to use the "cast a void pointer to packed uint64_t pointer" approach. Then it will not fail me.
That works of course.
Thanks, Werner
/** \brief Read 32-bit variable from network byte-order. \param[in] u32 pointer to a variable. \return 32-bit result in host byte-order. */ uint32_t net_rd_u32 (const uint8_t *u32) { uint32_t retv; #ifndef __USE_UNALIGNED_ACCESS retv = u32[0] << 24; retv |= u32[1] << 16; retv |= u32[2] << 8; retv |= u32[3]; #else retv = ntohl(*(uint32_t *)u32); #endif return (retv); }
An important thing here is that different chips behaves differently depending on what memory interface that is used. And the same chip might have different rules for different memory regions.