Author Topic: ARM Unaligned data...  (Read 13681 times)

0 Members and 1 Guest are viewing this topic.

Offline westfwTopic starter

  • Super Contributor
  • ***
  • Posts: 4668
  • Country: us
ARM Unaligned data...
« on: August 17, 2019, 06:34:36 am »
Is there a well-known trick for reading unaligned data on the ARM chips that don't support it in hardware?Something more clever than N bytes worth of load/add/shift?
Code: [Select]
extern char buffer[20];

static inline unsigned long getlong(unsigned char p[]) {
    return p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24);
}

int main() {
    volatile unsigned int data = *(unsigned long *)&buffer[1];
    data = getlong(&buffer[1]);
}
 

Offline filssavi

  • Frequent Contributor
  • **
  • Posts: 433
Re: ARM Unaligned data...
« Reply #1 on: August 17, 2019, 07:42:08 am »
I’m not so familiar with arm architecture to be able to completely rule it out, but usually the restrictions on aligned transfers only is placed to greatly simplify addressing related logic, so it is very possible that without explicit support the core might not be physically able to do unaligned transfers (ie. there are not enough address lines, etc.)
 

Offline andersm

  • Super Contributor
  • ***
  • Posts: 1198
  • Country: fi
Re: ARM Unaligned data...
« Reply #2 on: August 17, 2019, 08:20:34 am »
I don't think there is, but examine GCC's output to see if it does something smart.

Offline Brutte

  • Frequent Contributor
  • **
  • Posts: 614
Re: ARM Unaligned data...
« Reply #3 on: August 17, 2019, 10:15:43 am »
I do not understand the code.
You are assigning long to int.

then (char data[4])
data[0] = buffer[1]
data[0] = buffer[1]
twice.

Also, you never use buffer[0].
Then why the data is volatile?

Either this is super clever or has more bugs than lines of code.

 

Online nctnico

  • Super Contributor
  • ***
  • Posts: 30367
  • Country: nl
    • NCT Developments
Re: ARM Unaligned data...
« Reply #4 on: August 17, 2019, 11:46:21 am »
Is there a well-known trick for reading unaligned data on the ARM chips that don't support it in hardware?Something more clever than N bytes worth of load/add/shift?
Code: [Select]
extern char buffer[20];

static inline unsigned long getlong(unsigned char p[]) {
    return p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24);
}

int main() {
    volatile unsigned int data = *(unsigned long *)&buffer[1];
    data = getlong(&buffer[1]);
}
No. Never ever cast multi-byte types onto buffers. It is a recipe for a dissaster! Always use shifting bytes. The added bonus is that your code is endian agnostic as well.

Unaligned access is prohibited on most architectures.
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline rhodges

  • Frequent Contributor
  • **
  • Posts: 364
  • Country: us
  • Available for embedded projects.
    • My public libraries, code samples, and projects for STM8.
Re: ARM Unaligned data...
« Reply #5 on: August 17, 2019, 12:10:05 pm »
Either this is super clever or has more bugs than lines of code.
It is an example. It is not supposed to do anything useful.

I am still a novice with ARM, but I think this is the only way to do a 32-bit load/store at an unaligned address. MIPS had two instructions for loading the top and bottom parts, but I don't remember ARM having that. ARM does allow cheap shifts, so it isn't as bad as you might think.

Code: [Select]
8000256:       7894            ldrb    r4, [r2, #2]
 8000258:       7853            ldrb    r3, [r2, #1]
 800025a:       78d1            ldrb    r1, [r2, #3]
 800025c:       eb03 2304       add.w   r3, r3, r4, lsl #8
 8000260:       7912            ldrb    r2, [r2, #4]
 8000262:       eb03 4301       add.w   r3, r3, r1, lsl #16
 8000266:       eb03 6302       add.w   r3, r3, r2, lsl #24
Currently developing embedded RISC-V. Recently STM32 and STM8. All are excellent choices. Past includes 6809, Z80, 8086, PIC, MIPS, PNX1302, and some 8748 and 6805. Check out my public code on github. https://github.com/unfrozen
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17830
  • Country: fr
Re: ARM Unaligned data...
« Reply #6 on: August 17, 2019, 12:54:38 pm »
Depending on how many accesses to the buffer your code will do and how large the buffer is, it can be much simpler and more efficient to just copy the non-aligned buffer to an aligned one first with memcpy().
 

Online nctnico

  • Super Contributor
  • ***
  • Posts: 30367
  • Country: nl
    • NCT Developments
Re: ARM Unaligned data...
« Reply #7 on: August 17, 2019, 12:59:44 pm »
Depending on how many accesses to the buffer your code will do and how large the buffer is, it can be much simpler and more efficient to just copy the non-aligned buffer to an aligned one first with memcpy().
That will only work if all the elements are truly aligned and the compiler is using packed arrays (*). I've seen this trick go wrong and it is extremely hard to debug. Things work well on a PC but an ARM may work with wrong values without giving an exception.

The best and most portable solution is to copy the buffer into a struct using byte shifts and then use the struct internally. Vice versa to create buffer from the struct. The point of mapping a buffer onto a struct is usually moot anyway. Most protocols use big endian byte ordering so you have to byte shift anyway.

(*) When working packed arrays some compilers may decide to access data on a byte level and start to emit code which uses byte shifting no matter whether the data is aligned or not. Needless to say this results in bulky & slow code.
« Last Edit: August 17, 2019, 01:03:01 pm by nctnico »
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17830
  • Country: fr
Re: ARM Unaligned data...
« Reply #8 on: August 17, 2019, 01:37:53 pm »
Well, obviously there are portability considerations. If you are guaranteed to work with only little-endian stuff though all the way with a target that is little-endian, that works. It can go wrong when people don't know what they are doing.

Quote
Most protocols use big endian byte ordering

Uh. Yeah. If you say so. Some existing ones do indeed, in which case you don't have a choice indeed. I've never made any of my own custom protocols big-endian though.

Obviously for a structure, you must make sure the relative alignment of members will be the same in the defined structure and in the unaligned buffer. Compiler attributes exist for that, and of course some care must be taken. Likewise, individual members must be encoded in the same way in both "views" of a given buffer. Not hard to guarantee if for instance you're enforcing little-endian all the way, and use IEEE754 FP numbers (which the huge majority of CPUs support).

And whatever you mean by "array", obviously same thing. Data inside your unaligned buffer must be laid out the same way as an aligned one.

So of course again some care must be taken, and of course this is not 100% portable. But whenever it can be used (which is quite often in practice, especially if you control the whole design), it's again pretty simple and much more efficient.

Note that (again with proper care) it's something relatively easy to do in C, but a lot less (or not at all even) in some other languages.

You're right in pointing out that one must be careful all conditions for using this are met though.
« Last Edit: August 17, 2019, 01:45:19 pm by SiliconWizard »
 
The following users thanked this post: Siwastaja

Offline legacy

  • Super Contributor
  • ***
  • !
  • Posts: 4415
  • Country: ch
Re: ARM Unaligned data...
« Reply #9 on: August 17, 2019, 03:05:09 pm »
Remember, processors with two-byte memory access granularity will throw an exception upon encountering an odd address. On the SONY Playstatio1 and Playstation2, If you were to read from or write to field b, you’d attempt to access an odd address, and the CPU would throw up a System Error, inviting you to press the one only button "Restart. Yikes!"

So, instead of laying out your fields just the way you wrote them, the compiler padded the structure so that things would reside at even addresses.

-> tell gcc (and the linker) how to do the job for you!
 

Offline legacy

  • Super Contributor
  • ***
  • !
  • Posts: 4415
  • Country: ch
Re: ARM Unaligned data...
« Reply #10 on: August 17, 2019, 03:17:16 pm »
Unaligned access is prohibited on most architectures.

On chips like PPC74xx, you have the Altivec engine embedded in the core, and ... soon or later you will learn that Altivec doesn’t support unaligned memory access and it works with sixteen-byte chunks at a time, and all addresses passed to Altivec must be sixteen-byte aligned, but if your address is not aligned ...  the hardware won’t throw an exception to warn you about the unaligned address, but rather it will simply ignore the lower four bits of the address and charges ahead, operating on the wrong address.

Are you scared now? The first time I understood what was the reason why my programs silently corrupted memory and returned incorrect results if you don’t explicitly make sure all your data is aligned, as I did on XINU ... when well, I was really shocked that it only happened *when* the Altivec was enabled, whereas it didn't happen on PPC405GP where there is only a software emulation (soft-float in kernel space) for the Math Coprocessor  :palm:
 

Offline legacy

  • Super Contributor
  • ***
  • !
  • Posts: 4415
  • Country: ch
Re: ARM Unaligned data...
« Reply #11 on: August 17, 2019, 03:34:27 pm »
p.s.
The behavior of Arm v5 on unaligned accesses is different from Arm v7's one. Arm v6 has a "legacy bit" in its config to force the behavior to be the same as the one on Arm v5. Arm v7 does not have an option like this.

so  :-//
 

Offline amyk

  • Super Contributor
  • ***
  • Posts: 9176
Re: ARM Unaligned data...
« Reply #12 on: August 17, 2019, 07:00:54 pm »
If the hardware doesn't support it you'll need to do (what it should've done) manually, i.e. two reads and shifts.
 

Offline andersm

  • Super Contributor
  • ***
  • Posts: 1198
  • Country: fi
Re: ARM Unaligned data...
« Reply #13 on: August 17, 2019, 07:14:10 pm »
MIPS had two instructions for loading the top and bottom parts, but I don't remember ARM having that.
No-one else has the LWL/LWR instructions (AFAIK), as they were patented by MIPS, and used as a weapon against unlicensed implementations. When the patent expired, supporting unaligned memory accesses was already cheap enough that no-one bothered to copy the scheme.

Online nctnico

  • Super Contributor
  • ***
  • Posts: 30367
  • Country: nl
    • NCT Developments
Re: ARM Unaligned data...
« Reply #14 on: August 17, 2019, 07:47:54 pm »
Unaligned access is prohibited on most architectures.

On chips like PPC74xx, you have the Altivec engine embedded in the core, and ... soon or later you will learn that Altivec doesn’t support unaligned memory access and it works with sixteen-byte chunks at a time, and all addresses passed to Altivec must be sixteen-byte aligned, but if your address is not aligned ...  the hardware won’t throw an exception to warn you about the unaligned address, but rather it will simply ignore the lower four bits of the address and charges ahead, operating on the wrong address.

Are you scared now? The first time I understood what was the reason why my programs silently corrupted memory and returned incorrect results if you don’t explicitly make sure all your data is aligned, as I did on XINU ... when well, I was really shocked that it only happened *when* the Altivec was enabled, whereas it didn't happen on PPC405GP where there is only a software emulation (soft-float in kernel space) for the Math Coprocessor  :palm:
Tell me about it. Last year I spend 3 or 4 days on chasing a bug caused by unaligned access in a project I inherited.
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline westfwTopic starter

  • Super Contributor
  • ***
  • Posts: 4668
  • Country: us
Re: ARM Unaligned data...
« Reply #15 on: August 18, 2019, 12:37:04 am »
Quote
Quote
Most protocols use big endian byte ordering
Uh. Yeah. If you say so.
The TCP/IP protocols are all big-endian.  That may be "most protocols" :-)
(but anyway, this is about alignment, not endianness.  Though you can maybe deal with both at the same time via getlong/putlong macros (but you have to be really careful about keeping to your API definitions.)


Quote
Unaligned access is prohibited on most architectures.

Quote
supporting unaligned memory accesses was already cheap enough that no-one bothered [with the MIPS hack]

Quote
processors with two-byte memory access granularity will throw an exception upon encountering an odd address.

Heh.  I love these discussions.


SOME architectures support unaligned accesses in hardware, in which case that's probably what you want to do.  Some architectures fault (which you can write a handler for, though that will be incredibly slow) or otherwise to NOT allow unaligned accesses.

ARM is interesting, because there are several different ARM "architectures" - ARMv7m (Cortex M3, M4, and M7) support unaligned accesses "most of the time", but ARMV6m (Cortex M0, M0+) and ARMv8m does "sometimes" (CM33, but not CM23.)


Quote
Never ever cast multi-byte types onto buffers. It is a recipe for a dissaster! Always use shifting bytes.
But...  If the HW supports it, it's probably the fastest way!  Certainly you ought to wrap up such accesses, for portability to "other" architectures...


Quote
examine GCC's output to see if it does something smart.

Nope.  Or rather, gcc is smart enough to recognize an "obviously unaligned" pointer, and produce icky-looking code (here, for M0):

Code: [Select]
    volatile unsigned int data = *(uint32_t *)&buffer[1];
   4:   4b0f            ldr     r3, [pc, #60]   ; (44 <main+0x44>)
   6:   1c5c            adds    r4, r3, #1
   8:   785a            ldrb    r2, [r3, #1]
   a:   789b            ldrb    r3, [r3, #2]
   c:   021b            lsls    r3, r3, #8
   e:   4313            orrs    r3, r2
  10:   78a2            ldrb    r2, [r4, #2]
  12:   0412            lsls    r2, r2, #16
  14:   4313            orrs    r3, r2
  16:   78e2            ldrb    r2, [r4, #3]
  18:   0612            lsls    r2, r2, #24
  1a:   4313            orrs    r3, r2
  1c:   9301            str     r3, [sp, #4]

(or for m4, "knowing" that it can:)
Code: [Select]
    volatile unsigned int data = *(uint32_t *)&buffer[1];
   4:   4c0a            ldr     r4, [pc, #40]   ; (30 <main+0x30>)
   6:   f854 3f01       ldr.w   r3, [r4, #1]
   a:   9301            str     r3, [sp, #4]

But it's not smart enough to know that a char* pointer passed to a function might be unaligned:
Code: [Select]
ATTR uint32_t getlongbasic(uint8_t  p[]) {
    return *(uint32_t*)p;
   0:   6800            ldr     r0, [r0, #0]
   2:   4770            bx      lr

And you need to take special steps (annoyingly kludgey...):
Code: [Select]
ATTR uint32_t getlongunaligned(uint8_t  p[]) {
    return ((unaligned_uint32_t *)p)->i;
   0:   7802            ldrb    r2, [r0, #0]
   2:   7843            ldrb    r3, [r0, #1]
   4:   021b            lsls    r3, r3, #8
   6:   4313            orrs    r3, r2
   8:   7882            ldrb    r2, [r0, #2]
   a:   0412            lsls    r2, r2, #16
   c:   4313            orrs    r3, r2
   e:   78c0            ldrb    r0, [r0, #3]
  10:   0600            lsls    r0, r0, #24
  12:   4318            orrs    r0, r3
  14:   4770            bx      lr
  16:   46c0            nop                     ; (mov r8, r8)


The CM0+ code is about the same as I get with explicit or/shift logic:
Code: [Select]
ATTR uint32_t getlong(uint8_t p[]) {
    return p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24);
   0:   7803            ldrb    r3, [r0, #0]
   2:   7842            ldrb    r2, [r0, #1]
   4:   0212            lsls    r2, r2, #8
   6:   189a            adds    r2, r3, r2
   8:   7883            ldrb    r3, [r0, #2]
   a:   041b            lsls    r3, r3, #16
   c:   18d2            adds    r2, r2, r3
   e:   78c3            ldrb    r3, [r0, #3]
  10:   061b            lsls    r3, r3, #24
  12:   18d0            adds    r0, r2, r3
  14:   4770            bx      lr
  16:   46c0            nop                     ; (mov r8, r8)

CM4 makes use of the 32bit thumb2 instructions, but takes the same number of bytes overall:
Code: [Select]
ATTR uint32_t getlong(uint8_t p[]) {
    return p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24);
   0:   7803            ldrb    r3, [r0, #0]
   2:   7842            ldrb    r2, [r0, #1]
   4:   eb03 2302       add.w   r3, r3, r2, lsl #8
   8:   7882            ldrb    r2, [r0, #2]
   a:   eb03 4302       add.w   r3, r3, r2, lsl #16
   e:   78c0            ldrb    r0, [r0, #3]
  10:   eb03 6000       add.w   r0, r3, r0, lsl #24
  14:   4770            bx      lr
  16:   bf00            nop


In terms of "clever" I was thinking of things like double-word loads (LDRD, LDM) and shift, but it looks like the ARMs without HW unaligned access also lack LDRD, and none have 64bit shifts or divides, so ... probably not.  (or maybe checking for halfword-alignment and doing halfwords at a time...)


Current source code:
Code: [Select]
#include <stdint.h>

#define ATTR __attribute__((noinline))

extern char buffer[20];

typedef struct {
    uint32_t i;
} __attribute((__packed__)) unaligned_uint32_t;

ATTR uint32_t getlongbasic(uint8_t  p[]) {
    return *(uint32_t*)p;
}

ATTR uint32_t getlongunaligned(uint8_t  p[]) {
    return ((unaligned_uint32_t *)p)->i;
}

ATTR uint32_t getlong(uint8_t p[]) {
    return p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24);
}

ATTR uint32_t getlong1(uint8_t p[]) {
    return p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
}

int main() {
    volatile uint32_t data = *(uint32_t *)&buffer[1];
    data = *(uint32_t *)&buffer[2];

    data = getlongbasic(&buffer[1]);
    data = getlongunaligned(&buffer[1]);
    data = getlong(&buffer[1]);
    data = getlong1(&buffer[1]);
}
 
The following users thanked this post: edavid

Offline andersm

  • Super Contributor
  • ***
  • Posts: 1198
  • Country: fi
Re: ARM Unaligned data...
« Reply #16 on: August 18, 2019, 09:16:44 am »
Current source code: (snip)
Slightly cleaner:
Code: [Select]
typedef uint32_t unaligned_uint32_t __attribute__((aligned(1)));

ATTR uint32_t getlongunaligned(uint8_t  p[]) {
    return *(unaligned_uint32_t*)p;
}
 
The following users thanked this post: edavid

Offline cv007

  • Super Contributor
  • ***
  • Posts: 1067
Re: ARM Unaligned data...
« Reply #17 on: August 18, 2019, 01:06:49 pm »
https://godbolt.org/z/l3OLnp
(cortex-m0 version)

So where is the advantage. It all looks the same except for the longbasic version, which has to be a called function (noinline) or else it reverts to a byte version like the rest. I don't know what clock cycles are involved, but I would guess even the called longbasic version would end up being at least close to the icky versions (and only be correct 25% of the time of course).

It seems the gcc arm compiler has a resistance to unaligned access, and will be hard to 'trick' it to do something bad. On top of that, you have a bunch of flavors of arm mcu's, and it seems like its best to let the compiler handle all the various flavors and let it figure out what it takes to get you what you want for a particular mcu. I imagine the compiler authors would already be doing something clever if the resulting code required less cycles to do and was always correct.

I think you are just exploring the 'what if's' and 'why's', but maybe tell us when this comes into play. If a byte buffer has lots of word data to access, wouldn't the buffer be designed in a way to make word aligned access the norm? If its an occasional word access in a byte buffer, the icky code probably is a rare event and the mcu will not care as at 40MHz+ it has no time to think about it, and the extra few bytes of code will not be noticed inside the 32k+ flash.

Maybe I'm missing something, and fail to see it.
 

Online nctnico

  • Super Contributor
  • ***
  • Posts: 30367
  • Country: nl
    • NCT Developments
Re: ARM Unaligned data...
« Reply #18 on: August 18, 2019, 01:14:34 pm »
I rather handle it myself so I know the compiler isn't screwing up. I've seen unaligned accesses going wrong with GCC for ARM so it is better not to bet on whether the compiler does it right or not.
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline mfro

  • Regular Contributor
  • *
  • Posts: 226
  • Country: de
Re: ARM Unaligned data...
« Reply #19 on: August 18, 2019, 01:23:33 pm »
Current source code: (snip)
Slightly cleaner:
Code: [Select]
typedef uint32_t unaligned_uint32_t __attribute__((aligned(1)));

ATTR uint32_t getlongunaligned(uint8_t  p[]) {
    return *(unaligned_uint32_t*)p;
}

You might get away with such constructs many times, but strictly according to the standard, it invokes undefined behaviour.
Beethoven wrote his first symphony in C.
 
The following users thanked this post: nctnico

Offline andersm

  • Super Contributor
  • ***
  • Posts: 1198
  • Country: fi
Re: ARM Unaligned data...
« Reply #20 on: August 18, 2019, 02:58:07 pm »
Current source code: (snip)
Slightly cleaner:
Code: [Select]
typedef uint32_t unaligned_uint32_t __attribute__((aligned(1)));

ATTR uint32_t getlongunaligned(uint8_t  p[]) {
    return *(unaligned_uint32_t*)p;
}

You might get away with such constructs many times, but strictly according to the standard, it invokes undefined behaviour.
The __attribute__ syntax is a GCC extension, the standard does not apply.

Offline mfro

  • Regular Contributor
  • *
  • Posts: 226
  • Country: de
Re: ARM Unaligned data...
« Reply #21 on: August 18, 2019, 03:15:35 pm »
You might get away with such constructs many times, but strictly according to the standard, it invokes undefined behaviour.
The __attribute__ syntax is a GCC extension, the standard does not apply.

I'm not talking about __attribute__, I'm talking about 'upcasting'/aliasing
Beethoven wrote his first symphony in C.
 

Offline andersm

  • Super Contributor
  • ***
  • Posts: 1198
  • Country: fi
Re: ARM Unaligned data...
« Reply #22 on: August 18, 2019, 03:20:18 pm »
I'm not talking about __attribute__, I'm talking about 'upcasting'/aliasing
Maybe try understanding what's going on before posting.

Offline mfro

  • Regular Contributor
  • *
  • Posts: 226
  • Country: de
Re: ARM Unaligned data...
« Reply #23 on: August 18, 2019, 04:01:54 pm »
I'm not talking about __attribute__, I'm talking about 'upcasting'/aliasing
Maybe try understanding what's going on before posting.

I guess I do understand what this is about.

I also do know about the use of __attribute__(aligned(xx)), but - to my understanding - you appear to use it to tell the compiler it might need to access a variable through a pointer with a smaller than 'naturally' required alignment for its data type.

Not only I am unable to find any mention of such usage in the gcc documentation (but will be happy if you could point me to such), but I also can't see that this usage would suddenly make upcasting a char pointer (that has no proper alignment for uint32_t) to an uint32_t pointer legal.

My understanding is, that this is still UB.
Beethoven wrote his first symphony in C.
 

Offline andersm

  • Super Contributor
  • ***
  • Posts: 1198
  • Country: fi
Re: ARM Unaligned data...
« Reply #24 on: August 18, 2019, 04:28:20 pm »
Not only I am unable to find any mention of such usage in the gcc documentation (but will be happy if you could point me to such), but I also can't see that this usage would suddenly make upcasting a char pointer (that has no proper alignment for uint32_t) to an uint32_t pointer legal.
I am not accessing the array via an uint32_t pointer, I am accessing it through an unaligned_uint32_t pointer. The unaligned_uint32_t type has a minimum alignment requirement of 1 byte. The documentation can be found at https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/Common-Type-Attributes.html#index-aligned-type-attribute

Quote
My understanding is, that this is still UB.
It is an extension to the standard. A compiler can extend the standard in any way it sees fit.
 
The following users thanked this post: SiliconWizard


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

- - -
-->