PropGCC: Problems garbage collecting functions
SRLM
Posts: 5,045
Apparently, GCC does not automatically remove unused functions. This is unfortunate, especially due to the limited size of Propeller code space.
This is my dive into optimizing away those unused functions. My conclusion? I need help to finish...
The compiler by default does not remove functions. I suppose that this is because, when the .o files are generated, it doesn't know which functions are going to be called. So it leaves them all in. It's the linker that can remove these functions. This is done with the --gc-sections option ("garbage collect sections").
By default, all code lives in a single section. You can get around that by using -ffunction-sections on the g++ compile script. This will put each function into it's own section (".text.<FUNCTION>"). Likewise, -fdata-sections will put each chunk of data into it's own section. With C++, here is an example of the new section names:
The Problem: When you compile with the linker garbage collection, it removes all of the sections: everything from .boot, .lmmkernel, and .init to everything in between. As far as I can tell, it removed everything. When I download the elf, it is now 4 bytes and fails it's checksum.
You can see which sections are removed by the "-Wl,--print-gc-sections" option.
So, we need to indicate to the linker which sections to keep. The only way that I could find was to use the KEEP(*) notation inside of the linker script. The default linker script that propeller-elf-g++ uses can be found by adding the -Wl,-verbose option to the command. I just copied the file, and put it in a file called "main.ld".
To use the linker script instead of the default, you must specify "-Wl,--script=main.ld".
I modified the script by adding the KEEP(*) directive around any section that looked important. In the end, I put it around .boot, .lmmkernel, .kernel, .init, .data, .data*, .rodata, and .rodata*
Example:
By doing all that, I get to the following error:
Here is what is removed by the linker:
It looks like the problem is in the removal of ".fds.cog". My solution to fix this is to use the following the .ld file, under SECTIONS:
So, I'm stuck. Any suggestions? Also, it would be good if we didn't have to have a custom linker script for each project (or at all!).
This is my dive into optimizing away those unused functions. My conclusion? I need help to finish...
The compiler by default does not remove functions. I suppose that this is because, when the .o files are generated, it doesn't know which functions are going to be called. So it leaves them all in. It's the linker that can remove these functions. This is done with the --gc-sections option ("garbage collect sections").
'--gc-sections' decides which input sections are used by examining symbols and relocations. The section containing the entry symbol and all sections containing symbols undefined on the command-line will be kept, as will sections containing symbols referenced by dynamic objects. Note that when building shared libraries, the linker must assume that any visible symbol is referenced. Once this initial set of sections has been determined, the linker recursively marks as used any section referenced by their relocations.
By default, all code lives in a single section. You can get around that by using -ffunction-sections on the g++ compile script. This will put each function into it's own section (".text.<FUNCTION>"). Likewise, -fdata-sections will put each chunk of data into it's own section. With C++, here is an example of the new section names:
.text._ZN7fdsSpin6RxtimeEi .text._ZN7fdsSpin3DecEi .text._ZN7fdsSpin3HexEiiNote that you can tell this comes from FullDuplexSerial (rxtime, dec, and hex functions).
The Problem: When you compile with the linker garbage collection, it removes all of the sections: everything from .boot, .lmmkernel, and .init to everything in between. As far as I can tell, it removed everything. When I download the elf, it is now 4 bytes and fails it's checksum.
You can see which sections are removed by the "-Wl,--print-gc-sections" option.
So, we need to indicate to the linker which sections to keep. The only way that I could find was to use the KEEP(*) notation inside of the linker script. The default linker script that propeller-elf-g++ uses can be found by adding the -Wl,-verbose option to the command. I just copied the file, and put it in a file called "main.ld".
To use the linker script instead of the default, you must specify "-Wl,--script=main.ld".
I modified the script by adding the KEEP(*) directive around any section that looked important. In the end, I put it around .boot, .lmmkernel, .kernel, .init, .data, .data*, .rodata, and .rodata*
Example:
.boot : { KEEP(*(.boot)) } >hub /* the LMM kernel that is loaded into the cog */ .lmmkernel : { KEEP(*(.lmmkernel)) KEEP(*(.kernel))
By doing all that, I get to the following error:
(.text._ZN7fdsSpin5StartEiiii+0xb4): undefined reference to `__load_start_fds_cog'
Here is what is removed by the linker:
propeller-elf-g++ -Wl,--static -Wl,--gc-sections -Wl,--print-gc-sections -Wl,--script=main.ld -o main.elf main.o fds.o /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.fds.cog' in file 'fds.o' /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN7fdsSpin7RxflushEv' in file 'fds.o' /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN7fdsSpin6RxtimeEi' in file 'fds.o' /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN7fdsSpin3DecEi' in file 'fds.o' /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN7fdsSpin3HexEii' in file 'fds.o' /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN7fdsSpin3BinEii' in file 'fds.o' /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.dtors' in file '/opt/parallax/lib/gcc/propeller-elf/4.6.1/_crtend.o' fds.o: In function `fdsSpin::Start(int, int, int, int)': (.text._ZN7fdsSpin5StartEiiii+0xb4): undefined reference to `__load_start_fds_cog' collect2: ld returned 1 exit status make: *** [Test] Error 1
It looks like the problem is in the removal of ".fds.cog". My solution to fix this is to use the following the .ld file, under SECTIONS:
.fds : { KEEP(*(.fds*)) } >coguserIt keeps the .fds.cog section, but then creates problems overlapping memory:
propeller-elf-g++ -Wl,--static -Wl,--gc-sections -Wl,--print-gc-sections -Wl,--script=main.ld -o main.elf main.o fds.o /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN7fdsSpin7RxflushEv' in file 'fds.o' /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN7fdsSpin6RxtimeEi' in file 'fds.o' /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN7fdsSpin3DecEi' in file 'fds.o' /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN7fdsSpin3HexEii' in file 'fds.o' /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN7fdsSpin3BinEii' in file 'fds.o' /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.dtors' in file '/opt/parallax/lib/gcc/propeller-elf/4.6.1/_crtend.o' /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: section .boot loaded at [0000000000000000,000000000000001f] overlaps section .fds loaded at [0000000000000000,0000000000000193] fds.o: In function `fdsSpin::Start(int, int, int, int)': (.text._ZN7fdsSpin5StartEiiii+0xb4): undefined reference to `__load_start_fds_cog' collect2: ld returned 1 exit status make: *** [Test] Error 1
So, I'm stuck. Any suggestions? Also, it would be good if we didn't have to have a custom linker script for each project (or at all!).
Comments
I think also that the compiler will remove static functions that are never called, so if you have functions that doesn't need to be external (visible to other .c files) then declare those as "static". The optimizer will also be more aggressive about inlining static functions as well, so it will be better for performance as well as size.
Eric
Test Setup
I have a simple program that starts a FFDS1 driver (converted from Spin with spin2cpp v1.02), and outputs a message to the terminal. Files:
-FFDS1.spin: original serial driver file, annotated with volatile
-main.cpp: main file
-FFDS1.cpp/.h: generated file (either with or without --gas, see below)
-main.ld: the linker script (mostly the default, but with KEEP() (see above))
-main: the downloader script, calls propeller-load then opens a terminal
-make: the makefile
What Works:
If I convert FFDS1.spin with no options (no --gas), then the garbage collection works correctly:
What Does NOT Work:
If I convert the FFDS1.spin file with the --gas, I get the following errors: Notice that it removes the .FFDS1.cog section in the GC process. I can prevent that by adding to the main.ld script a KEEP() directive, and that prevents the .FFDS1.cog section from being cut. Unfortunately, the linker still produces the "undefined reference" errors as before.
I've also tried defining the array without the extern, and giving it a size of 2048. I have tried adding volatile. Both failed to make a difference.
Questions
- How does the __asm__ section (the actual bytes) make it into the _load_start_FFDS1_cog arrary?
- Are there any references for how to define an array like this? All the asm tutorials I could find were for inline asm, not asm in an array.
- If _load_start_FFDS1_cog is extern, where is it defined normally?
- How can I solve the undefined reference problem?
I looked into this, but I couldn't figure out how to tell GCC to put each function into a separate .o file. I wouldn't want to do that by hand, since that's a huge maintenance issue.
My motivation for doing this is to get "free" code size reduction. Without any effort by the programmer, the generated code could be much smaller.
For example, I tested the program in post #4 (version with the dat block instead of --gas) with and without garbage collection, and here are the results: With this code, the garbage collection was able to save over a KB. I think this is particularly important in creating "library" files for the propeller, since many people will probably only use a subset of functions that are provided.
That's traditionally how it's done in C. Pruning will not work if everything is in one .c file.
--Steve
It's all in main.ld. I tried a version of the program that used the PropGCC printf instead of FFDS1, and that required KEEP around: FFDS1 required a bit more: I don't recall if I've tried minimizing this list, but it's pretty short as it is. The only really questionable one is .data, but I'm thinking that, as a general rule, the linker shouldn't be removing any of the data sections anyway since PASM code may be interacting with it. This would mean that we don't use the -fdata-sections flag to put each data element into it's own section.
In any case, I don't think these KEEP sections should be included just yet, since we haven't worked out exactly what needs to stay and how to do GAS keeps. Any suggestions on how to keep the GAS symbols?
It seems a natural and good practice to me to keep a bunch of related functions in a single file. Assuming things are not growing to unmanageable proportions. All the functions acting on some data structure or device for example. All the methods of a class perhaps.
Having to manage a collection of source files or a library seems an unnecessary and messy burden.
And what about all those "private" functions that I would normally hide within a module with "static"?
On ARM, that's the vector table, and the reset vector points to the __start function. From there, that referenced everything necessary to pull in the whole application. I imagine, here, you'd use __start directly.