Can GCC print interim results?

Check out the code below:

#include <avr/io.h> const uint16_t baudrate = 9600; void setupUART( void ) { uint16_t ubrr = ( ( F_CPU / ( 16 * (float) baudrate ) ) - 1 + .5 ); UBRRH = ubrr >> 8; UBRRL = ubrr & 0xff; } int main( void ) { setupUART(); } 

This is the command used to compile the code:

 avr-gcc -g -DF_CPU=4000000 -Wall -Os -Werror -Wextra -mmcu=attiny2313 -Wa,-ahlmns=project.lst -c -o project.o project.cpp 

ubrr calculated by the compiler as 25, as far as it is good. However, to check what the compiler computes, I looked at the disassembly list.

 000000ae <setupUART()>: ae: 12 b8 out UBRRH, r1 ; 0x02 b0: 89 e1 ldi r24, 0x19 ; 25 b2: 89 b9 out UBRRL, r24 ; 0x09 b4: 08 95 ret 

Is it possible to make avr-gcc print the intermediate result at compile time (or extract information from the .o file), so when I compile the code, it prints a line like (uint16_t) ubbr = 25 or similar? Thus, I can perform a quick health check during calculations and settings.

+7
source share
4 answers

GCC has command line options to request that it unload its intermediate representation after any compilation step. Dumps of the tree are in the pseudo-C syntax and contain the information you need. For what you are trying to do, the dumps -fdump-tree-original and -fdump-tree-optimized occur at points of -fdump-tree-optimized in the optimization pipeline. I do not have an AVR compiler, so I modified your test script to be standalone and compiled with the compiler that I have:

 typedef unsigned short uint16_t; const int F_CPU = 4000000; const uint16_t baudrate = 9600; extern uint16_t UBRRH, UBRRL; void setupUART(void) { uint16_t ubrr = ((F_CPU / (16 * (float) baudrate)) - 1 + .5); UBRRH = ubrr >> 8; UBRRL = ubrr & 0xff; } 

and then

 $ gcc -O2 -S -fdump-tree-original -fdump-tree-optimized test.c $ cat test.c.003t.original ;; Function setupUART (null) ;; enabled by -tree-original { uint16_t ubrr = 25; uint16_t ubrr = 25; UBRRH = (uint16_t) ((short unsigned int) ubrr >> 8); UBRRL = ubrr & 255; } $ cat test.c.149t.optimized ;; Function setupUART (setupUART, funcdef_no=0, decl_uid=1728, cgraph_uid=0) setupUART () { <bb 2>: UBRRH = 0; UBRRL = 25; return; } 

You can see that constant-expression folding is done so early that it already happened in the “source” dump (which is the earliest acceptable dump you can have), and this optimization has further shifted the shift operations and masks into statements that write UBRRH and UBRRL.

The numbers in the file names (003t and 149t) are likely to be different for you. If you want to see all the tree-like dumps, use -fdump-tree-all . There are also “RTL” dumps that are not like C and probably not useful to you. If you're interested, -fdump-rtl-all on. In total, there are about 100 trees and 60 RTL dumps, so it is recommended to do this in the directory from scratch.

(Psssst: every time you put spaces inside your parentheses, God kills the kitten.)

+3
source

There may be a solution for printing intermediate results, but it will take you some time to implement. Therefore, it only costs for a rather large source code base.

You can customize your GCC compiler; either through a plugin (painfully encoded in C or C ++), or through the MELT extension. MELT is a high-level, Lisp-like, domain-specific language for the GCC extension. (It is implemented as a plugin [meta-] for GCC and translated into C ++ - code suitable for GCC).

However, this approach requires you to understand the GCC internals, then add your own “optimization” pass to do aspect programming (for example, using MELT) to print the corresponding intermediate results.

You can also look at not only the generated assembly (and use -fverbose-asm -S as options for GCC), but also, possibly, in the generated Gimple views (possibly with -fdump-tree-gimple ). For some interactive tools, consider a graphical MELT probe .

Perhaps adding your own inline (with the MELT extension), such as __builtin_display_compile_time_constant , may be relevant.

+2
source

I doubt there is an easy way to determine what the compiler does. There may be some tools in gcc to flush out the intermediate form of the language, but it will definitely not be easy to read, and if you DO NOT REALLY suspect that the compiler is doing something wrong (and it has a VERY little example to show it), it is unlikely you can use it for anything meaningful - simply because there is too much work to keep track of what is happening.

A better approach is to add temporary variables (and possibly fingerprints) to your code if you are worried about their correctness:

  uint16_t ubrr = ( ( F_CPU / ( 16 * (float) baudrate ) ) - 1 + .5 ); uint8_t ubrr_high = ubrr >> 8 uint8_t ubrr_low = ubrr & 0xff; UBRRH = ubrr_high; UBRRL = ubrr_low; 

Now, if you have a non-optimized build and step by step in GDB, you can see what it does. Otherwise, adding printouts of some code to the code to show what values ​​...

If you cannot print it on the target system because you are in the process of setting up the uart that you will use to print, then copy the code on your local host system and debug it there. If the compiler is not very buggy, you should get the same values ​​from the same compilation.

+1
source

Hack here: just automate what you do manually.

  • In your makefile, verify that avr-gcc is disassembling ( -ahlms= output.lst). Also, use your own disassembly method as a step after compilation in your makefile.
  • As a step after compilation, process the file with your favorite scripting language to look for out UBRRH and out UBRRL . They will be loaded from registers, so your script can output immediately preceding assignments to registers that will be loaded into UBRRH and UBRRL . The script can then collect the UBRR value from the value loaded into the general registers that are used to set UBRRH and UBRRL .

It sounds easier than Bazile Starynkevich a very useful suggestion for the MELT extension. Now, given that this solution seems fragile, blush first, so consider this problem:

  • We know that (at least on your processor) the out UBRR_, r__ will appear in the disassembly list: there simply is no other way to set the registers / write data to the port. One thing that can change is the distance between these lines and around them, but this can be easily dealt with with a script
  • We also know that out instructions can only be executed from general registers, so we know that a general register will exist as the second argument to the out statement string, so this should not be a problem.
  • Finally, we also know that this register will be set before the out command. Here we must allow some variability: instead of LDI (load immediately), avr-gcc can create some other set of instructions for setting the value of the register. I think that as the first pass, the script should be able to parse the immediate download and otherwise reset all the last found command with a register to be written to the UBRR_ ports.

the script may change if you change platforms (some processors have UBRRH1 / 2 register instea UBRRH , however in this case you will have to change the baud code. If the script complains that it cannot parse the disassembly, then at least you know that Your check has not been executed.

+1
source

All Articles