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/MSP430FR5969: bit shift causing compiler warning

Part Number: MSP430FR5969

Tool/software: TI C/C++ Compiler

I am calculating a crc over a memory range bounded by MPUSEGB1. After assigning the register value and shifting left by 4 bits, I got a compiler warning. After debugging I found that the bit shift is the root cause of the issue.

compiler warning:

warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
   uint16_t * end_addr   = (uint16_t *)(0x0f00 << 4);

code:

uint16_t * end_addr   = (uint16_t *)(0x0f00 << 4);             // causes warning
uint16_t * end_addr   = (uint16_t *)((uint16_t)(0x0f00 << 4)); // causes warning
uint16_t * end_addr   = (uint16_t *)((0x0f00 << 4) & 0xffff);  // causes warning
uint16_t * end_addr   = (uint16_t *)(0xf000);                  // does not cause warning

The values compute correctly even with the warning so I know the operations work as intended, but for some reason they always draw a warning from the compiler unless I explicitly use "0xF000". What causes this behavior?

  • Hello EmbeddedMike,

    What compiler version are you using?

    I tried what you had and all work except:

    uint16_t * end_addr   = (uint16_t *)((0x0f00 << 4) & 0xffff);  Warning #69-D integer conversion resulted in a change of sign

    so I added cast to fix that

    uint16_t * end_addr   = (uint16_t *)((uint16_t)(0x0f00 << 4) & 0xffff);

    But that doesn't address the fact that for you the first three versions generate a warning.

  • Thanks for the quick response. My compiler version: msp430-gcc-7.3.2.154

  • I suspect that you are compiling with option -mlarge which makes pointers 20 bits. Since integer constants are 16 bits that triggers the warning unless you use a 32 bit constant: (0x0f00L << 4).

    Why it fails to issue a warning for the last case I cannot explain. Some subtle difference in the parser between a simple constant and more complex expression perhaps.

  • No, I tried both small and large memory model, but now I see you are using MSP430-GCC compiler.

    Let me dig a little deeper on this.

  • Hi,

    GCC won't warn about integer to pointer conversions, when the integer is a constant. This is why there is no warning for your last line.

    Shifting a 1 into the the sign bit of an integer is considered to be integer overflow, which in turn means that GCC doesn't consider "(0xF00 << 4)" to be a constant expression, since the operation does shift 1 into the sign bit. If you use a smaller constant which doesn't result in a 1 being shifted into the sign bit, the warning won't be emitted.

    uint16_t * end_addr1  = (uint16_t *)(0x0F0 << 4);

    Or, if you pass "-std=c89", then GCC does not consider shifting 1 into the sign but to be integer overflow, and you won't get the warning.

    Finally, the best solution is just to indicate this is an unsigned integer constant by appending "U" to the value, as below. When shifting, the result of the shift in an expression like this is the type of the value being shifted, in this case an unsigned int. There is no "sign bit" so shifting a 1 into the MSB will not be considered overflow.

    uint16_t * end_addr   = (uint16_t *)(0x0f00U << 4);

    Regards,

  • You're the man. Adding the "U" suffix to the constant prevented the compiler warning. I'll mark this thread as resolved because this answer satisfies my original question.

    I abstracted a bit of the problem away in order to focus on the shifting aspect. In my actual project I'm trying to use the register value of MPUSEGB1 because I'd like to avoid having magic numbers. Since I can't append a "U" to a variable I can't use this solution as is. I tried to do something equivalent:

    uint16_t * end_addr   = (uint16_t *)(((uint16_t)MPUSEGB1) << 4);

    By casting MPUSEGB1 to unsigned integer I expected that the shift would not be treated as an overflow but I'm still getting the warning. Similarly, assigning to an unsigned int first and then shifting also produces the warning:

    uint16_t rom_end = MPUSEGB1;
    uint16_t * end_addr   = (uint16_t *)(rom_end << 4);

    Is there anyway to avoid this warning when using variables?

  • This is where the __int20 type comes in handy. GCC will always warn when converting between variable integers and pointers of different sizes because truncating addresses or leaving undefined bits in an address is potentially dangerous. GCC will only ever automatically convert between "full" integer types (i.e. 16-bit or 32-bit), so without an explicit cast to __int20, the size will never be quite right.

    Try the below code, which casts the result of a shift to a 20-bit unsigned integer (i.e. the underlying type of a pointer) before casting that integer to your desired pointer type.

    end_addr1  = (uint16_t *)(unsigned __int20)(MPUSEGB1 << 4)

    Hope this helps,

  • That's perfect. I really appreciate your help, excellent as always.

  • I would cast it to the 20 bit type before shifting since segment boundaries can be in upper FRAM.

      end_addr = (uint16_t *)(((__int20)MPUSEGB1)<<4);

    But the code generated is not good considering that a movx.w and rlam.a would do the job.

        4514:       40 18 1c 42     movx.w  &0x005a6,r12    ;
        4518:       a6 05 
    
    0000451a <.Loc.6.1>:
        451a:       00 18 4d 4c     movx.a  r12,    r13     ;
        451e:       0c 4c           mov     r12,    r12     ;
        4520:       0f 18 4d 11     rpt #16 { rrax.a        r13             ;
        4524:       b0 13 8e 45     calla   #17806          ;0x0458e
    
    00004528 <.Loc.7.1>:
    }
        4528:       0d 12           push    r13             ;
        452a:       0c 12           push    r12             ;
        452c:       0c 16           popm.a  #1,     r12     ;20-bit words
    

    That rips the 20 bit int into two 16 bit pieces before calling a utility routine to shift the 32 bit value 4 places. Then turns it back into a 20 bit register value. Ugh.

  • Fortunately for my case, MPUSEGB1 will always be in lower FRAM but I think this is a good point. Unless I'm misunderstanding, you seem concerned that shifting first would push the top 4 bits out of the 16 bit uint which would result in a data loss if the border address is in high FRAM (MPUSEGx = 0x1200 for instance).

    That lead me to wonder, where do the top bits go when you shift? Functionally I always think of them just "disappearing" but how does that actually work in memory? Is there a standard way to handle bit shifting or does it depend on hardware implementations?

  • The msb usually goes into a carry bit. (See instruction set description for details.) Very handy if you need to shift something larger than one word. Not visible to C code of course.

    With instructions that shift by more than one bit, all but one bit that gets shifted out will be lost.

    This function should do the job without all of that conversion to 32 bits and back nonsense:

    void *test(void)
    {
      asm("movx.w &MPUSEGB1,r12");
      asm("rlam.a #4,r12");
    }

  • David Schultz36 said:

    I would cast it to the 20 bit type before shifting since segment boundaries can be in upper FRAM.

      end_addr = (uint16_t *)(((__int20)MPUSEGB1)<<4);

    Nice spot. It seems that shift arguments will get promoted up to an int before shifting, but not beyond that, even if the lvalue is larger than an int.

    I think I've spotted a gap in the MSP430 GCC backend that could address the poor code being generated. I'm out of the office for a week, but will take a look once I get back.

    Thanks,

  • It would certainly be nice if the compiler better supported the __int20 type. Those conversions to 32 bits and back are painful to read.

    I looked through the GCC user guide (SLAU646e) and found no mention of this type so perhaps that should be updated as well.

  • Hello EmbeddedMike,

    I haven’t heard from you for a couple of days now, so I’m assuming you have your answer and are moving forward with your project.
    If this isn’t the case, please click the "This did NOT resolve my issue" button and reply to this thread with more information.
    If this thread locks, please click the "Ask a related question" button and in the new thread describe the current status of your issue and any additional details you may have to assist us in helping to solve your issues.

    Also, I would like to thank everyone who has contributed to this posting.  Your responses have been very helpful and informative. This is great example of community support.

  • Hi Dennis,

    On my end I see that the post is "resolved" and I've accepted two separate answers from Jozef as the answers to my question. The main forum page also shows this post as resolved. Is this post still marked as pending on your end?

    In any case, my question has been answered and I'm appreciative of all who contributed.

    Cheers,
    EM

**Attention** This is a public forum