I am using a Microchip microcontroller which defines the following union:
__extension__ typedef struct tagT1CONBITS {
union {
struct {
uint16_t :1;
uint16_t TCS:1;
uint16_t TSYNC:1;
uint16_t :1;
uint16_t TCKPS:2;
uint16_t TGATE:1;
uint16_t :6;
uint16_t TSIDL:1;
uint16_t :1;
uint16_t TON:1;
};
struct {
uint16_t :4;
uint16_t TCKPS0:1;
uint16_t TCKPS1:1;
};
};
} T1CONBITS;
extern volatile T1CONBITS T1CONbits __attribute__((__sfr__));
Somewhere in my code I am defining a variable as a 8 bit unsigned integer which I would like to assign to one of the fields of the union above. Somewhat as follows:
uint8_t tckps;
// The value of tckps is calculated here by some magic formula
tckps = magicformula();
// We asign the value of tckps to the uC register
T1CONbits.TCKPS = tckps;
I have the -Wconversion option enabled in gcc wich leads to the following warning:
warning: conversion to 'volatile unsigned char:2' from 'uint8_t' may alter its value
I can understand why gcc is warning me. I am currently perfoming a value check on the tckps variable before asigning it so I know that the data loss is not going to be a problem, but I don't know how to satisfy gcc conversion check so that it doesn't warn me in this particular case.
How can I fix the warning?
Thanks in advance!
EDIT: Added toolchain information.
Microchip Language Tool Shell Version 1.33 (Build date: Oct 9 2017).
Copyright (c) 2012-2016 Microchip Technology Inc. All rights reserved
*** Executing: "C:\Program Files (x86)\Microchip\xc16\v1.33\bin\bin/elf-gcc.exe"
"-v"
Using built-in specs.
COLLECT_GCC=C:\Program Files (x86)\Microchip\xc16\v1.33\bin\bin/elf-gcc.exe
Target: pic30-elf
Configured with: /home/xc16/release-builds/build_20171009/src/XC_GCC/gcc/configure --build=i386-linux --host=i386-mingw32 --target=pic30-elf --disable-lto --disable-threads --disable-libmudflap --disable-libssp --disable-libstdcxx-pch --disable-hosted-libstdcxx --with-gnu-as --with-gnu-ld --enable-languages=c --disable-nls --disable-libgomp --without-headers --disable-libffi --disable-bootstrap --prefix=/bin --libexecdir=/bin --program-prefix=pic30- --with-libelf=/home/xc16/release-builds/build_20171009/bin/XC_GCC-elf-mingw32-xclm/host-libs/ --with-dwarf2 --with-gmp=/home/xc16/release-builds/build_20171009/bin/XC_GCC-elf-mingw32-xclm/host-libs --with-ppl=/home/xc16/release-builds/build_20171009/bin/XC_GCC-elf-mingw32-xclm/host-libs --with-cloog=/home/xc16/release-builds/build_20171009/bin/XC_GCC-elf-mingw32-xclm/host-libs --with-zlib=/home/xc16/release-builds/build_20171009/bin/XC_GCC-elf-mingw32-xclm/host-libs --with-bugurl=http://www.microchip.com/support --with-host-libstdcxx=-Wl,-Bstatic,-lstdc++,-Bdynamic,-lm
Thread model: single
gcc version 4.5.1 (XC16, Microchip v1.33) Build date: Oct 9 2017 (Microchip Technology)
This lets met get rid of the warning:
#include <stdint.h>
typedef struct tagT1CONBITS {
union {
struct {
uint16_t :1;
uint16_t TCS:1;
uint16_t TSYNC:1;
uint16_t :1;
uint16_t TCKPS:2;
uint16_t TGATE:1;
uint16_t :6;
uint16_t TSIDL:1;
uint16_t :1;
uint16_t TON:1;
};
struct {
uint16_t :4;
uint16_t TCKPS0:1;
uint16_t TCKPS1:1;
};
};
} T1CONBITS;
volatile T1CONBITS T1CONbits;
uint8_t (*magicformula)(void);
int main(void)
{
uint8_t tckps;
// The value of tckps is calculated here by some magic formula
tckps = magicformula() ;
// We asign the value of tckps to the uC register
T1CONbits.TCKPS = (uint8_t)(tckps & 3); // This fixes the warning
return 0;
}
I compile it with:
gcc -Wall -Wconversion test2.c
The problem I see is that the compiler can't check over the function boundaries that the range of the variable is not exceeded. If you do it at the point of use the compiler can check this.
The cast is to avoid a warning when the expression is promoted to int.
This is a known issue 39170 with the gcc compiler specifically, when compiling with -Wconversion. The problem exists from gcc 4.3.x and later versions. It is possible that they have fixed the issue in some newer version, but I couldn't find any information regarding it.
A possible dirty work-around is to mask the value bit a bit mask as shown in the answer by Wolfgang.
You can selectively ignore gcc warnings with #pragma GCC diagnostic. Below is an example of how you can use this:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
T1CONbits.TCKPS = tckps;
#pragma GCC diagnostic pop
The push pragma stores the current state of diagnostic warnings. The ignored pragma then tells the compiler to ignore the specified warning from that point on. The pop pragma then restores the prior diagnostic state, so any other places where a conversion warning might occur will be printed.
The end result is that the warning is suppressed for only the specific source lines between the pragmas.
Related
I have a piece of code looking like this:
void update_clock(uint8_t *time_array)
{
time_t time = *((time_t *) &time_array[0]); // <-- hangs
/* ... more code ... */
}
Where time_array is an array of 4 bytes (i.e. uint8_t time_array[4]).
I'm using arm-none-eabi-gcc to compile this for an STM32L4 processor.
While compiling this a couple of months ago I got no errors and the code is running perfectly fine on all my test MCUs. I did some updates to my environment (OpenSTM32) when coming back to this project and now this piece of code is crashing on some MCUs while working fine on others.
I still have my binary from a couple of months ago and have confirmed that this code path works fine on all of my MCUs (I have about 5 to test on), but now it works on two of them while causing a crash on three of them.
I have mitigated the problem by rewriting the code like this:
time_t time = (
((uint32_t) time_array[0]) << 0 |
((uint32_t) time_array[1]) << 8 |
((uint32_t) time_array[2]) << 16 |
((uint32_t) time_array[3]) << 24
);
While this works for now, I think the old code looks cleaner and I'm also worried that if this code path hangs I probably will have similar errors elsewhere.
Does anyone have any idea what can be causing this? Can I change anything in my setup to make the compiler work the old way again?
From version 7-2017-q4-major, arm gcc ships with newlib compiled with time_t defined as 64 bit (long long) integer, causing all sorts of problems with code that assumes it to be 32 bits. Your code is reading past the end of the source array, taking whatever is stored there as the high order bits of the time value, possibly resulting in a date before the big bang, or after the heat death of the universe, which might not be what your code expects.
If the source array is known to contain 32 bits of data, copy it to a 32 bit int32_t variable first, then you can assign it to a time_t, this way it will be properly converted, regardless of the size of time_t.
Your development environment OpenSTM32 may be using a gcc compiler. If so, gcc supports the following macro flag.
-fno-strict-aliasing
It you are using -O2, this flag might resolve your problem.
Using memcpy is the standard advice, and is sometimes optimized-away by the compiler:
memcpy(&time, time_array, sizeof time);
Finally, you can use gcc's typeof and a compound literal with a union to generate the following safe cast:
#define PUN_CAST4(a, x) ((union {uint8_t src[4]; typeof(x) dst;}){{a[0],a[1],a[2],a[3]}}).dst
time_t time = PUN_CAST4(time_array, time);
As an example, the following code is compiled at https://godbolt.org/g/eZRXxW:
#include <stdint.h>
#include <time.h>
#include <string.h>
time_t update_clock(uint8_t *time_array) {
time_t t = *((time_t *) &time_array[0]); // assumes no alignment problem
return t;
}
time_t update_clock2(uint8_t *time_array) {
time_t t =
(uint32_t)time_array[0] << 0 |
(uint32_t)time_array[1] << 8 |
(uint32_t)time_array[2] << 16 |
(uint32_t)time_array[3] << 24;
return t;
}
time_t update_clock3(uint8_t *time_array) {
time_t t;
memcpy(&t, time_array, sizeof t);
return t;
}
#define PUN_CAST4(a, x) ((union {uint8_t src[4]; typeof(x) dst;}){{a[0],a[1],a[2],a[3]}}).dst
time_t update_clock4(uint8_t *time_array) {
time_t t = PUN_CAST4(time_array, t);
return t;
}
gcc 8.1 is good for all four examples: it generates the trivial code with -O2. But gcc 7.3 is bad for the 4th. Clang is also good for all four with -m32 for a 32-bit target, but fails on the 2nd and 4th without it
Your issue is caused by unaligned access, or writing to the wrong area.
Compiling
#include "stdint.h"
#include "time.h"
time_t myTime;
void update_clock(uint8_t *time_array)
{
myTime = *((time_t *) &time_array[0]); // <-- hangs
/* ... more code ... */
}
with GCC 7.2.1 with the arguments -march=armv7-m -Os generates the following
update_clock(unsigned char*):
ldr r3, .L2
ldrd r0, [r0]
strd r0, [r3]
bx lr
.L2:
.word .LANCHOR0
myTime:
Because your time array is an 8 bit type there are no rules for alignment, so if the linker has not word aligned it, when you try and dereference it as a time_t * the LDRD instruction is given a non word aligned address and causes a usagefault.
The LDRD and STRD instructions are loading and storing 8 bytes, whereas your array is only 4 bytes long. I suggest you check sizeof(time_t) in your environment, and make an aligned area long enough to store it.
I have a code-block, which is written in C for IAR C/C++ Compiler.
__no_init uint8_t u8ramPhysStart_startUp # 0x20000000;
__no_init uint8_t u8ramPhysEnd_startUp # 0x2002FFFF;
__no_init uint8_t u8ramTestStart_startUp # 0x20004008;
__no_init uint8_t u8ramTestEnd_startUp # 0x20008008;
#define START_ASM (&u8ramPhysStart_startUp)
#define RAMSTART_STRTUP ((uint32_t)START_ASM)
My goal is converting it or rather making it GCC compatible. For this, I rewrite above code like:
unsigned char u8ramPhysStart_startUp __asm("# 0x20000000");
unsigned char u8ramPhysEnd_startUp __asm("# 0x2002FFFF");
unsigned char u8ramTestStart_startUp __asm("# 0x20004008");
unsigned char u8ramTestEnd_startUp __asm("# 0x20008008");
But after compilation I get following error:
C:\Users\Pc\AppData\Local\Temp\ccyuCWQT.s: Assembler messages:
C:\Users\Pc\AppData\Local\Temp\ccyuCWQT.s:971: Error: expected symbol name
C:\Users\Pc\AppData\Local\Temp\ccyuCWQT.s:972: Error: expected symbol name
Do someone knows, what it means?
I believe the gcc code should be something like
uint8_t __attribute__ ((section(".my_section"))) u8ramPhysStart_startUp;
where .my_section is something you have added to the linker script.
That being said, the only way which you can make allocation at absolute addresses portable, is to stick to pure ISO C:
#define u8ramPhysStart_startUp (*(volatile uint8_t*)0x20000000u)
or in case you want a pointer to an address:
#define u8ramPhysStart_startUp ((volatile uint8_t*)0x20000000u)
The disadvantage of this is that it doesn't actually allocate any memory, but relies on a linker script to handle that part. That's preferable in most cases.
Another disadvantage is that you won't be able to view these "variable" names in a debugger, since they are actually not variables at all. And that's the main reason why some tool chains come up with things like the non-standard # syntax.
I see code like following : (legecy code in the project I am working on)
#if __GNUC__ > 3
.ipv6_addr = {.__in6_u = {.__u6_addr32 = {0, 0, 0, 0}}}
#else
.ipv6_addr = {.in6_u = {.u6_addr32 = {0, 0, 0, 0}}}
#endif
where "ipv6_addr" is in type of struct in6_addr. I don't understand why its member in6_u would change to "in6_u" if __GNUC > 3.
My question is: why / when GCC version could impact the name of the field in struct in6_addr ?
Thanks.
Update: my host system has GCC 4.1.2, but the in6_addr was defined as :
in /usr/include/netinet/in.h
/* IPv6 address */
struct in6_addr
{
union
{
uint8_t u6_addr8[16];
uint16_t u6_addr16[8];
uint32_t u6_addr32[4];
} in6_u;
#define s6_addr in6_u.u6_addr8
#define s6_addr16 in6_u.u6_addr16
#define s6_addr32 in6_u.u6_addr32
};
And gcc version is:
$/usr/bin/gcc -v
Using built-in specs.
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-libgcj-multifile --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --disable-plugin --with-java-home=/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre --with-cpu=generic --host=x86_64-redhat-linux
Thread model: posix
gcc version 4.1.2 20080704 (Red Hat 4.1.2-50)
The structure definition of concern here, struct in6_addr, comes from the C runtime library (in <netinet/in.h>). The C runtime library is usually tightly coupled with the operating system in use, but not the C compiler in use. This is especially true for the C compilers that define __GNUC__ to any value (there are at least three of these: GCC, clang, and icc); these compilers are designed to be usable with many different OSes and runtimes. Therefore, in principle, testing __GNUC__ does not tell you anything useful about structure definitions that come from the runtime.
I suspect that the authors of this "legacy code" tested on two different Linux distributions, noted an accidental correlation between the value of __GNUC__ and the contents of <netinet/in.h>, and didn't bother looking for a more correct way to make their code compile.
You should replace the entire conditional with this:
.ipv6_addr = IN6ADDR_ANY_INIT;
The macro IN6ADDR_ANY_INIT is required by the relevant standard (POSIX.1-2008 spec for <netinet/in.h>) to be usable as an initializer for a variable of type in6_addr, setting that variable to the IPv6 wildcard address, which is all-bits-zero. So it will have the same effect without requiring any #ifdefs at all.
To illustrate that #if __GNUC__ > 3 is the wrong test to apply here, here are three different definitions of struct in6_addr, all taken from systems where you might reasonably encounter both __GNUC_==3 and __GNUC__==4 (the 3.x series is getting a little old nowadays, but I still run into it from time to time).
GNU libc 2.17
struct in6_addr
{
union
{
uint8_t __u6_addr8[16];
#if defined __USE_MISC || defined __USE_GNU
uint16_t __u6_addr16[8];
uint32_t __u6_addr32[4];
#endif
} __in6_u;
};
NetBSD 6.1
struct in6_addr {
union {
__uint8_t __u6_addr8[16];
__uint16_t __u6_addr16[8];
uint32_t __u6_addr32[4];
} __u6_addr; /* 128-bit IP6 address */
};
Windows 7 (official SDK; perversely does not provide netinet/in.h):
struct in6_addr {
union {
UCHAR Byte[16];
USHORT Word[8];
} u;
};
Given this code:
int main(void)
{
__asm volatile ("jmp %eax");
return 0;
}
32-bit TCC will complain with:
test.c:3: error: unknown opcode 'jmp'
but the 64-bit version will compile just fine.
What's the problem with the 32 bit code?
The solution is to simply add a star (*) before the register, like this:
__asm volatile ("jmp *%eax");
I'm not exactly sure what the star means. According to this SO post:
The star is some syntactical sugar indicating that control is to be passed indirectly, by reference/pointer.
As for why it works with 64-bit TCC, I assume that it's a bug; 64-bit GCC complains with Error: operand type mismatch for 'jmp', as it should.
Summary
I'm porting ST's USB OTG Library to a custom STM32F4 board using the latest version of Sourcery CodeBench Lite toolchain (GCC arm-none-eabi 4.7.2).
When I compile the code with -O0, the program runs fine. When I compile with -O1 or -O2 it fails. When I say fail, it just stops. No hard fault, nothing (Well, obviously there is something it's doing but I don't have a emulator to use to debug and find out, I'm sorry. My hard fault handler is not being called).
Details
I'm trying to make a call to the following function...
void USBD_Init(USB_OTG_CORE_HANDLE *pdev,
USB_OTG_CORE_ID_TypeDef coreID,
USBD_DEVICE *pDevice,
USBD_Class_cb_TypeDef *class_cb,
USBD_Usr_cb_TypeDef *usr_cb);
...but it doesn't seem to make it into the function body. (Is this a symptom of "stack-smashing"?)
The structures passed to this function have the following definitions:
typedef struct USB_OTG_handle
{
USB_OTG_CORE_CFGS cfg;
USB_OTG_CORE_REGS regs;
DCD_DEV dev;
}
USB_OTG_CORE_HANDLE , *PUSB_OTG_CORE_HANDLE;
typedef enum
{
USB_OTG_HS_CORE_ID = 0,
USB_OTG_FS_CORE_ID = 1
}USB_OTG_CORE_ID_TypeDef;
typedef struct _Device_TypeDef
{
uint8_t *(*GetDeviceDescriptor)( uint8_t speed , uint16_t *length);
uint8_t *(*GetLangIDStrDescriptor)( uint8_t speed , uint16_t *length);
uint8_t *(*GetManufacturerStrDescriptor)( uint8_t speed , uint16_t *length);
uint8_t *(*GetProductStrDescriptor)( uint8_t speed , uint16_t *length);
uint8_t *(*GetSerialStrDescriptor)( uint8_t speed , uint16_t *length);
uint8_t *(*GetConfigurationStrDescriptor)( uint8_t speed , uint16_t *length);
uint8_t *(*GetInterfaceStrDescriptor)( uint8_t speed , uint16_t *length);
} USBD_DEVICE, *pUSBD_DEVICE;
typedef struct _Device_cb
{
uint8_t (*Init) (void *pdev , uint8_t cfgidx);
uint8_t (*DeInit) (void *pdev , uint8_t cfgidx);
/* Control Endpoints*/
uint8_t (*Setup) (void *pdev , USB_SETUP_REQ *req);
uint8_t (*EP0_TxSent) (void *pdev );
uint8_t (*EP0_RxReady) (void *pdev );
/* Class Specific Endpoints*/
uint8_t (*DataIn) (void *pdev , uint8_t epnum);
uint8_t (*DataOut) (void *pdev , uint8_t epnum);
uint8_t (*SOF) (void *pdev);
uint8_t (*IsoINIncomplete) (void *pdev);
uint8_t (*IsoOUTIncomplete) (void *pdev);
uint8_t *(*GetConfigDescriptor)( uint8_t speed , uint16_t *length);
uint8_t *(*GetUsrStrDescriptor)( uint8_t speed ,uint8_t index, uint16_t *length);
} USBD_Class_cb_TypeDef;
typedef struct _USBD_USR_PROP
{
void (*Init)(void);
void (*DeviceReset)(uint8_t speed);
void (*DeviceConfigured)(void);
void (*DeviceSuspended)(void);
void (*DeviceResumed)(void);
void (*DeviceConnected)(void);
void (*DeviceDisconnected)(void);
}
USBD_Usr_cb_TypeDef;
I've tried to include all the source code relevant to this problem. If you want to see the entire source code you can download it here: http://www.st.com/st-web-ui/static/active/en/st_prod_software_internet/resource/technical/software/firmware/stm32_f105-07_f2_f4_usb-host-device_lib.zip
Solutions Attempted
I tried playing with #pragma GCC optimize ("O0"), __attribute__((optimize("O0"))), and declaring certain definitions as volatile, but nothing worked. I'd rather just modify the code to make it play nicely with the optimizer anyway.
Question
How can I modify this code to make it play nice with GCC's optimizer?
There doesn't seem to be anything wrong with the code you showed, so this answer will be more general.
What are typical errors with "close to hardware" code that works properly unoptimized and fails with higher optimization levels?
Think about the differences between -O0 and -O1/-O2: optimization strategies are - among others - loop unrolling (doesn't seem to be dangerous), attempting to hold values in registers as long as possible, dead code elimination and instruction reordering.
improved register usage typically leads to problems with higher optimization levels if hardware registers that can change anytime aren't declared volatileproperly (see PokyBrain's comment above). The optimized code will try to hold values in registers as long as possible resulting in your program failing to notice changes on the hardware side. Make sure to declare hardware registers volatile properly
dead code elimination will likely lead to problems if you need to read a hardware register to produce whatever effect on the hardware not known to the compiler and don't do anything with the value you just read. These hardware accesses might be optimized away if you don't declare the variable used for read access void properly (compiler should issue a warning, though). Make sure to cast dummy reads to (void)
instruction reordering: if you need to access different hardware registers in a certain sequence to produce the desired results and if you do that through pointers not related in any way otherwise, the compiler is free to reorder the resulting instructions as it sees fit (even if hardware registers are properly declared volatile). You will need to stray memory barriers into your code to enforce the required access sequence (__asm__ __volatile__(::: "memory");). Make sure to add memory barriers where needed.
Although unlikely, it might still be the case that you found a compiler bug. Optimization is not an easy job, especially when it comes close to hardware. It might be worth a peek into the gcc bug database.
If all this doesn't help, you sometimes just can't avoid to dig into the generated assembler code to make sure its doing what it is supposed to do.