This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Compiler/MSP432P401R: Indirect assignment of non-aligned uint64 causes runtime fault

Part Number: MSP432P401R

Tool/software: TI C/C++ Compiler

Hi all,

TI v16.9.0 LTS compiler.

I'm having some trouble with non-aligned access.  Here is an example:

typedef struct
{
  uint8_t a_u8;
  uint64_t b_u64;
  uint8_t e_u8;
} __attribute__ ((packed)) myStruct;

myStruct m_st;

uint8_t f_u8;
uint8_t *g_u8_ptr;

uint64_t c_u64;
uint64_t *d_u64_ptr;

// uint8_t non aligned access 
f_u8 = 0xaa;
g_u8_ptr = &(m_st.e_u8);   // e_u8 is not 32bit aligned

m_st.e_u8 = f_u8;          // Direct access works
*g_u8_ptr = g_u8_ptr;      // Indirect access works
  
// uint64_t non aligned access 
c_u64 = 0xabcdabcdabcdabcd;
d_u64_ptr = &(m_st.b_u64); // b_u64 is not 32bit aligned.
m_st.b_u64 = c_u64; // Direct access works

*d_u64_ptr = c_u64; // Indirect access causes crash

There is no problem with assigning (direct or indirect) a non-aligned uint8_t - the compiler knows how to handle this.

But for uint64_t I'm finding that an indirect assignment causes the micro to crash.

I'm running this in a TIRTOS project so I'm seeing a System_exit() with the following dump:

FSR = 0x0100
HFSR = 0x40000000
DFSR = 0x00000001
MMAR = 0xe000ed34
BFAR = 0xe000ed38
AFSR = 0x00000000

Cheers

Julian

  • I have yet to find any authoritative documentation on this point.  But I'm pretty sure this statement ...

    Julian Kolodko said:
    d_u64_ptr = &(m_st.b_u64); // b_u64 is not 32bit aligned.

    ... causes a violation of the strict aliasing rule.  This rule is very detailed, but the bottom line is a type used to perform a memory access must be a close match to the type used to define the original variable.  In this statement, you copy the address of a packed member of structure to a general non-packed pointer.  Any later use of the pointer violates the rule.

    Thanks and regards,

    -George

  • G'Day George,

    I accept that what I'm doing is naughty at best.  (And I'm guessing this problem is deeper than I understand - both the TI compiler and GCC have the same problem).

    But, superficially at least, if a compiler can detect the 8 bit non-aligned indirect access and generate working code couldn't the compiler at least generate a warning in the case of the non-aligned indirect 64 bit access??

    Indeed, as the example below shows, 8bit, 16bit and 32bit access ALL work and do exactly what you expect despite indirect access through non-aligned pointers.  Why not 64bit?

    typedef struct
    {
      uint8_t  d1_u8;  // mess up alignment
      uint64_t t_u64;
      uint8_t  t_u8;
      uint8_t  d2_u8;  // mess up alignment
      uint16_t t_u16;
      uint32_t t_u32;
    } __attribute__ ((packed)) myStruct;

        myStruct m_st;

        uint8_t data_u8;
        uint8_t *t_u8_ptr;

        uint16_t data_u16;
        uint16_t *t_u16_ptr;

        uint32_t data_u32;
        uint32_t *t_u32_ptr;

        uint64_t data_u64;
        uint64_t *t_u64_ptr;


        // uint8_t non aligned access
        data_u8 = 0xab;
        t_u8_ptr = &(m_st.t_u8);   // t_u8_ptr is not 32bit aligned

        m_st.t_u8 = data_u8;       // Direct access works
        *t_u8_ptr = data_u8;       // Indirect access works


        // uint16_t non aligned access
        data_u16 = 0xabcd;
        t_u16_ptr = &(m_st.t_u16); // t_u16_ptr is not 32bit aligned

        m_st.t_u16 = data_u16;     // Direct access works
        *t_u16_ptr = data_u16;     // Indirect access works


        // uint32_t non aligned access
        data_u32 = 0xabcdabcd;
        t_u32_ptr = &(m_st.t_u32); // t_u32_ptr is not 32bit aligned

        m_st.t_u32 = data_u32;     // Direct access works
        *t_u32_ptr = data_u32;     // Indirect access works


        // uint64_t non aligned access
        data_u64 = 0xabcdabcdabcdabcd;
        t_u64_ptr = &(m_st.t_u64); // t_u64_ptr is not 32bit aligned.

        m_st.t_u64 = data_u64;     // Direct access works
        *t_u64_ptr = data_u64;     // Indirect access causes crash; 

  • Hi George,

    Poking around, I've found the following in the compiler manual (SPNU151N  section 5.16.4)

    "It is illegal to implicitly or explicitly cast the address of a packed struct member as a pointer to any non-packed type except an unsigned char. "

    If I understand this right (and I'm not sure I am, because I don't think there is any type casting going on here), it means the following lines of my example are illegal.

    t_u16_ptr = &(m_st.t_u16);  

    t_u32_ptr = &(m_st.t_u32);

    t_u64_ptr = &(m_st.t_u64);

    Which is fine - but if it is illegal, should there be a warning or error????

  • Whether or not there is an explicit cast, there is a pointer type conversion in each of those assignments. The text in the manual is a little bit loose with the technical language; the crucial point is you can't access an object through a pointer to a differently-packed type unless one of the types is pointer to unsigned char.
  • Julian Kolodko said:
    I've found the following in the compiler manual

    Kudos to you for finding that.  I'm usually the one who finds info like that.

    Julian Kolodko said:

    "It is illegal to implicitly or explicitly cast the address of a packed struct member as a pointer to any non-packed type except an unsigned char. "

    If I understand this right (and I'm not sure I am, because I don't think there is any type casting going on here)

    You are correct to say no explicit casts are used.  But there are implicit casts.  Implicit casts are common.  In this expression ...

    int_var + char_var

    ... char_var is implicitly cast to type int.  More specific to your example, in this statement ...

    t_u64_ptr = &(m_st.t_u64); 

    ... the address of a packed 64-bit unsigned int is implicitly cast to the address of a normal (not packed) 64-bit unsigned int.

    Why does only the 64-bit wide access fail?  The 8-bit wide access works because 8-bit wide instructions work on every address.  The 16-bit wide and 32-bit wide accesses work because the instructions used rely on special HW features to perform the correct access even when the address is unaligned.  The 64-bit wide access uses different instructions which lack those HW features.  For more background on these instructions and how they relate to alignment, please see this article from ARM.  Note that article also mentions build switches and keywords available with the ARM compiler.  None of those details apply to the TI compiler.

    Julian Kolodko said:
    if it is illegal, should there be a warning or error????

    It is not illegal in the same sense that a missing semi-colon is illegal.  It would probably be more clear if the word undefined were used instead of illegal.  Catching violations of this restriction is more difficult than it first appears.  Consider this example of a violation, taken from the compiler manual ...

    void foo(int *param);
    struct packed_struct ps;
    
    foo(&ps.i);
    

    The actual violation takes place within foo.  

    Thanks and regards,

    -George

  • Thank you George,

    I've learnt a lot!
    I didn't realise that "packed" is part of the type, and that hardware takes care of things up to 32bits.

    Thanks for your time.

    Cheers
    Julian