Could any of you experienced chaps suggest on which of these are worth enabling?
I am using -Og now, and I get 160k (down from 225k with -O0 and max debugs) in Debug build versus 179k in Release build with -O3. How is that possible?
As a habit, I use -Og for debug compiles: the code remains eminently debuggable, but a lot of pointelss memory/register shuffling and pushing/popping is removed.
uint8_t ssa_buf[512];
SSA_read(0, ssa_buf);
uint32_t size1 = ssa_buf[4]|(ssa_buf[5]<<8)|(ssa_buf[6]<<16)|(ssa_buf[7]<<24);
uint32_t size2 = ssa_buf[8]|(ssa_buf[9]<<8)|(ssa_buf[10]<<16)|(ssa_buf[11]<<24);
I do pass some parms by address but very rarely and only if necessary to do the job (like the crc accumulator in a one byte at a time crc func).
) issue. Not that I don't enjoy the discussion, there is always room to learn and refine one's knowledge! asm("nop");
asm("nop");
asm("nop");
uint32_t size1 = ssa_buf[4]|(ssa_buf[5]<<8)|(ssa_buf[6]<<16)|(ssa_buf[7]<<24);
uint32_t size2 = ssa_buf[8]|(ssa_buf[9]<<8)|(ssa_buf[10]<<16)|(ssa_buf[11]<<24);
asm("nop");
asm("nop");
asm("nop");

// Hang around for delay in ms. Approximate but doesn't need interrupts etc working.
static void hang_around(uint32_t delay)
{
uint32_t fred = 17000*delay;
while (fred>0)
{
fred--;
}
}

As it happens, I am looking for someone to do well defined portions of this current project too but it is too early to do that since the API is not all done and documented. I do a lot of googling of course but sometimes a focused Q on a good forum is a good way, and the vast majority of stuff online is garbage... all the way to code examples which could not have ever worked. I have always used forums, for various topics, not just electronics, and run one myself. But I am learning. It is a steep curve in places. I am documenting everything too, with lots of comments in the code (most progs don't comment C at all) 
__attribute__((noinline, section(".ramfunc")))
void delay_cycles(uint32_t cycles)
{
cycles /= 4;
asm volatile (
"1: sub %[cycles], %[cycles], #1 \n"
" nop \n"
" bne 1b \n"
: [cycles] "+l"(cycles)
);
}
The instruction will be either sub or subs depending on the core type. The code assumes no wait states, so placed in SRAM. But you can obviously leave it in the flash, just adjust the division factor.
Is that better because the asm will not get modified by optimisation?
I guess fred will get put into a register...
OK, yes, disassembly shows the code is there; 'code' is held in a register. I am very familiar with asm
Amusingly, I think this is where one issue is: a delay func for use where there is no timer tickCode: [Select]// Hang around for delay in ms. Approximate but doesn't need interrupts etc working.
static void hang_around(uint32_t delay)
{
uint32_t fred = 17000*delay;
while (fred>0)
{
fred--;
}
}
Putting volatile there makes a lot of stuff work
newbrain - the reason I post a lot is because I have nobody else I can ask. I am working more or less alone. I have nobody who is available as a "C consultant". I have actually tried to set something like that up (via my little business) but nobody is interested. I do a lot of googling of course but sometimes a focused Q on a good forum is a good way. I have always used forums, for various topics, not just electronics. But I am learning. It is a steep curve in places. I am documenting everything too, with lots of comments in the code (most progs don't comment C at all).
)5.1.2.3 Program execution
1 The semantic descriptions in this International Standard describe the behavior of an abstract machine in which issues of optimization are irrelevant.
2 Accessing a volatile object, modifying an object, modifying a file, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression in general includes both value computations and initiation of side effects. Value computation for an lvalue expression includes determining the identity of the designated object.
3 [ I'll spare you the definition of sequencing ]
4 In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).
5 [ signal handling ]
6 The least requirements on a conforming implementation are:
— Accesses to volatile objects are evaluated strictly according to the rules of the abstract machine.
— At program termination, all data written into files shall be identical to the result that execution of the program according to the abstract semantics would have produced.
— The input and output dynamics of interactive devices shall take place as specified in 7.21.3. The intent of these requirements is that unbuffered or line-buffered output appear as soon as possible, to ensure that prompting messages actually appear prior to a program waiting for input.
This is the observable behavior of the program.
... the vast majority of stuff online is garbage... all the way to code examples which could not have ever worked.
Is there a command-line switch to stop this interpretation of "standard" functions? (globally, or on a per-function basis?)Sure. "-fno-builtin" for all of them, and then there are flags like "-fno-builtin-memcpy".
volatile void* vmemcpy(volatile void* dst, const volatile void* src, size_t len) {
volatile char* dst0 = (volatile char*)dst;
const volatile char* src0 = (const volatile char*)src;
while (len-- > 0) { *dst0++ = *src0++; } // or asm volatile (...);
return dst;
}
Instead of replacing memcpy(), which is only confusing since it already has well-understood behavior.// Hang around for delay in ms. Approximate but doesn't need interrupts etc working.
__attribute__((noinline))
static void hang_around(uint32_t delay)
{
delay *= (SystemCoreClock/4000);
asm volatile (
"1: subs %[delay], %[delay], #1 \n"
" nop \n"
" bne 1b \n"
: [delay] "+l"(delay)
);
}
Well, it could use the camera interface to scan the text on the crystal and do OCR on it... uint32_t offset=0;
uint32_t addr=0x08000000;
for ( uint32_t page=4100; page<=5119; page++ )
{
AT45dbxx_WritePage((uint8_t*)addr,512,page*512);
offset+=512;
addr+=512;
}
/**
* @brief Program word (32-bit) at a specified address.
* @note This function must be used when the device voltage range is from
* 2.7V to 3.6V.
*
* @note If an erase and a program operations are requested simultaneously,
* the erase operation is performed before the program one.
*
* @param Address specifies the address to be programmed.
* @param Data specifies the data to be programmed.
* @retval None
* Waits for previous operation to finish
*
*/
static void L_FLASH_Program_Word(uint32_t Address, uint32_t Data)
{
// wait for any previous op to finish
while(__HAL_FLASH_GET_FLAG(FLASH_FLAG_BSY) != RESET);
// clear program size bits
CLEAR_BIT(FLASH->CR, FLASH_CR_PSIZE);
// reload program size bits
FLASH->CR |= FLASH_PSIZE_WORD;
// enable programming
FLASH->CR |= FLASH_CR_PG;
// write the data in
*(volatile uint32_t*)Address = Data;
}
// ===== Make sure we can erase and program sector 11 - the top one =====
// If that works, the rest should work :)
uint32_t error=0;
uint32_t data;
uint32_t address;
// Erase sector
L_HAL_FLASH_Unlock();
L_FLASH_Erase_Sector(11);
L_HAL_FLASH_Lock();
// Check it is all FFs
for (address=0x080e0000; address<0x080fffff; address+=4)
{
data=*(volatile uint32_t*)address;
if ( data != 0xffffffff )
error++;
}
// Erase sector again
L_HAL_FLASH_Unlock();
L_FLASH_Erase_Sector(11);
// Fill it with data
for (uint32_t i=0; i<(128*1024); i+=4)
{
L_FLASH_Program_Word(i+0x080e0000, i);
}
// Probably always best to lock the flash again before reading it
L_HAL_FLASH_Lock();
// Check the data we have just written
data=0;
for (address=0x080e0000; address<0x080fffff; address+=4)
{
if ( (*(volatile uint32_t*)address) != data )
error++;
data+=4;
}
As regards other dodgy code, I wonder about this
.....
where addr is reading the CPU FLASH. Addr is being modified in the loop so could this be optimised out?
I then have this bit which tests the uppermost (128k) block of FLASH for erasure and programming. That is referencing FLASH memory addresses, but they are being declared volatile, but I wonder if correctly
On the other matter, which was variables being held in registers and thus the debug mode being unable to display their values as you step through the code, am I right that this is rather useless (because half the variables you wanted to watch are not visible anymore, with -Og) but the only way around it is to use -O0?
They may also have arguments declared "restrict", meaning the compiler will try to detect if they overlap. For a function like this that would also be undesirable.
// Write a number of bytes to a single page through the buffer with built-in erase.
// 1/8/21 The last parm is actually a linear address within the device.
bool AT45dbxx_WritePage(
const uint8_t *data, // In Data to write
uint16_t len, // In Length of data to write (in bytes)
uint32_t page // In linear address, a multiple of 512
) {
HAL_StatusTypeDef status = HAL_OK;
if (len==0) return (status == HAL_OK);
page = page << AT45dbxx.Shift;
at45dbxx_resume();
at45dbxx_wait_busy();
B_HAL_GPIO_WritePin(_45DBXX_CS_GPIO, _45DBXX_CS_PIN, GPIO_PIN_RESET);
at45dbxx_tx_rx_byte(AT45DB_MNTHRUBF1);
at45dbxx_tx_rx_byte((page >> 16) & 0xff);
at45dbxx_tx_rx_byte((page >> 8) & 0xff);
at45dbxx_tx_rx_byte(page & 0xff);
status = B_HAL_SPI_Transmit(&_45DBXX_SPI, (uint8_t *) data, len, AT45DB_SPI_TIMEOUT_MS);
B_HAL_GPIO_WritePin(_45DBXX_CS_GPIO, _45DBXX_CS_PIN, GPIO_PIN_SET);
at45dbxx_wait_busy();
return (status == HAL_OK);
}
// Read a number of bytes from a single page
// 1/8/21 The last parm is actually a linear address within the device. May have to be a
// multiple of 512, too.
// The device command used (0Bh) allows a continuous read of the whole device. This does not
// appear to be allowed here.
bool AT45dbxx_ReadPage(
uint8_t *data, // Out Buffer to read data to
uint16_t len, // In Length of data to read (in bytes)
uint32_t page // In linear address, a multiple of 512
) {
HAL_StatusTypeDef status = HAL_OK;
if (len==0) return (status == HAL_OK);
page = page << AT45dbxx.Shift;
// Round down length to the page size
if (len > AT45dbxx.PageSize) {
len = AT45dbxx.PageSize;
}
at45dbxx_resume();
at45dbxx_wait_busy();
B_HAL_GPIO_WritePin(_45DBXX_CS_GPIO, _45DBXX_CS_PIN, GPIO_PIN_RESET);
at45dbxx_tx_rx_byte(AT45DB_RDARRAYHF);
at45dbxx_tx_rx_byte((page >> 16) & 0xff);
at45dbxx_tx_rx_byte((page >> 8) & 0xff);
at45dbxx_tx_rx_byte(page & 0xff);
at45dbxx_tx_rx_byte(0);
status = B_HAL_SPI_Receive(&_45DBXX_SPI, data, len, AT45DB_SPI_TIMEOUT_MS);
B_HAL_GPIO_WritePin(_45DBXX_CS_GPIO, _45DBXX_CS_PIN, GPIO_PIN_SET);
return (status == HAL_OK);
}
and then you have to fool the compiler by decrementing a unit32_t around that loop). With the other optimisation options I was seeing weird stuff where e.g. I had a variety of if() tests in a sequence, and at some point it decided that the expression being tested had to be always false, but I could not see that at all. Obviously the compiler could be way more clever than me in detecting an "impossible-true" situation, but the fact is that all those conditionals were working correctly. I think the compiler was deciding that some flags (various bytes in a 512-byte buffer read from an eeprom) would always test false. Maybe that whole buffer should have been volatile, but that would not make sense to me if it fixed it. The bigger issue is that so much code could be broken in this way and retesting absolutely everything one has written in 6 months is just not viable. And some 90% of this project is libs from ST and such (ETH and USB) which are practically impenetrable and take months to do any debugging on, and debugging -O issues is very slow since it involves stepping through code, which is often impossible because the code is real-time.As an aside, could optimisation break code where you call a function which has say 4 parameters but the 4th one is never used inside that function?
You seem to be fixated to the idea that your program is fine or at least "almost" fine and it's the compiler that breaks it and then you need to kind of reverse-engineer what the compiler is doing to "break" the program then "adjust" the program until it gets "through" the compiler. This is all wrong, don't do it. But your way of debugging the issues amplifies this false premise.
Is there some way to see what the compiler is removing? I think this came up before and basically the answer was No.