Shop OBEX P1 Docs P2 Docs Learn Events
PropGCC: Problems garbage collecting functions — Parallax Forums

PropGCC: Problems garbage collecting functions

SRLMSRLM Posts: 5,045
edited 2013-04-01 20:53 in Propeller 1
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").
'--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._ZN7fdsSpin3HexEii
Note 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*)) } >coguser
It 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

  • David BetzDavid Betz Posts: 14,516
    edited 2012-11-26 20:02
    Typically, you get around this problem by creating a library where each function is in a separate .o file.
  • ersmithersmith Posts: 6,054
    edited 2012-11-27 04:54
    David's suggestion of using a library with each function in its own .o is a good one (and the usual approach for C).

    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
  • SRLMSRLM Posts: 5,045
    edited 2012-12-28 16:17
    After some more debugging, the problem seems to be with what spin2cpp produces for the --gas option. Namely, when garbage collection is on the linker does not like that the _load_start_FFDS1_cog[] is defined as "extern", but with no actual definition anywhere (?).

    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:
    ~/Downloads/serial_test$ make
    Makefile:30: .depend: No such file or directory
    rm -f ./.depend
    propeller-elf-g++ -mlmm -Wall -Os -m32bit-doubles -mfcache -fno-exceptions -fno-rtti -fpermissive -ffunction-sections -fno-strict-aliasing --MM FFDS1.cpp main.cpp>>./.depend;
    main.cpp: In function 'int main()':
    main.cpp:18:34: warning: invalid conversion from 'const char*' to 'int32_t {aka int}' [-fpermissive]
    FFDS1.h:22:11: warning:   initializing argument 1 of 'int32_t FFDS1::Str(int32_t)' [-fpermissive]
    rm -f *.o *.elf .depend *.map *.rawmap a.out
    propeller-elf-g++ -mlmm -Wall -Os -m32bit-doubles -mfcache -fno-exceptions -fno-rtti -fpermissive -ffunction-sections -fno-strict-aliasing   -c -o FFDS1.o FFDS1.cpp
    propeller-elf-g++ -mlmm -Wall -Os -m32bit-doubles -mfcache -fno-exceptions -fno-rtti -fpermissive -ffunction-sections -fno-strict-aliasing   -c -o main.o main.cpp
    main.cpp: In function 'int main()':
    main.cpp:18:34: warning: invalid conversion from 'const char*' to 'int32_t {aka int}' [-fpermissive]
    FFDS1.h:22:11: warning:   initializing argument 1 of 'int32_t FFDS1::Str(int32_t)' [-fpermissive]
    propeller-elf-g++ -mlmm -Wall -Os -m32bit-doubles -mfcache -fno-exceptions -fno-rtti -fpermissive -ffunction-sections -fno-strict-aliasing -Wl,--static -Wl,--print-gc-sections -Wl,--script=main.ld -Wl,--gc-sections -o main.elf FFDS1.o main.o
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS12TxEi' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS15TxbufEii' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS111TxbufnowaitEii' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS19WaitfortxEv' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS17RxflushEv' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS17RxcheckEv' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS16RxtimeEi' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS12RxEv' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS13DecEi' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS13HexEii' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS13BinEii' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS14AtoiEi' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS14HtoiEi' in file 'FFDS1.o'
    

    What Does NOT Work:
    If I convert the FFDS1.spin file with the --gas, I get the following errors:
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.FFDS1.cog' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS12TxEi' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS15TxbufEii' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS111TxbufnowaitEii' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS19WaitfortxEv' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS17RxflushEv' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS17RxcheckEv' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS16RxtimeEi' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS12RxEv' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS13DecEi' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS13HexEii' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS13BinEii' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS14AtoiEi' in file 'FFDS1.o'
    /opt/parallax/lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld: Removing unused section '.text._ZN5FFDS14HtoiEi' in file 'FFDS1.o'
    FFDS1.o: In function `FFDS1::Start(int, int, int)':
    (.text._ZN5FFDS15StartEiii+0x28): undefined reference to `__load_start_FFDS1_cog'
    FFDS1.o: In function `FFDS1::Start(int, int, int)':
    (.text._ZN5FFDS15StartEiii+0x30): undefined reference to `__load_start_FFDS1_cog'
    FFDS1.o: In function `FFDS1::Start(int, int, int)':
    (.text._ZN5FFDS15StartEiii+0x40): undefined reference to `__load_start_FFDS1_cog'
    FFDS1.o: In function `FFDS1::Start(int, int, int)':
    (.text._ZN5FFDS15StartEiii+0x48): undefined reference to `__load_start_FFDS1_cog'
    FFDS1.o: In function `FFDS1::Start(int, int, int)':
    (.text._ZN5FFDS15StartEiii+0x50): undefined reference to `__load_start_FFDS1_cog'
    FFDS1.o:(.text._ZN5FFDS15StartEiii+0x58): more undefined references to `__load_start_FFDS1_cog' follow
    collect2: ld returned 1 exit status
    make: *** [Debug] Error 1
    
    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?
  • SRLMSRLM Posts: 5,045
    edited 2012-12-28 16:24
    David Betz wrote: »
    Typically, you get around this problem by creating a library where each function is in a separate .o file.
    ersmith wrote: »
    David's suggestion of using a library with each function in its own .o is a good one (and the usual approach for C).

    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

    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:
    LMM:
    GAS/No-GC: 5308 bytes
    DAT/No-GC: 4764 bytes
    DAT/  -GC: 3508 bytes
    
    CMM:
    DAT/  -GC: 3152 bytes
    
    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.
  • jazzedjazzed Posts: 11,803
    edited 2012-12-28 17:18
    Putting related functions in separate .c files and using the linker will allow pruning unused code.
    That's traditionally how it's done in C. Pruning will not work if everything is in one .c file.

    --Steve
  • David BetzDavid Betz Posts: 14,516
    edited 2012-12-28 18:38
    SRLM wrote: »
    After some more debugging, the problem seems to be with what spin2cpp produces for the --gas option. Namely, when garbage collection is on the linker does not like that the _load_start_FFDS1_cog[] is defined as "extern", but with no actual definition anywhere (?).
    The linker creates these symbols semi-magically. Maybe there is something we can do to mark the corresponding sections as "KEEP" like you've done in the linker script. Also, I can change the default linker script to include these KEEP expressions. Can you tell me exactly which sections you surrounded with KEEP()?
  • SRLMSRLM Posts: 5,045
    edited 2012-12-28 20:01
    David Betz wrote: »
    The linker creates these symbols semi-magically. Maybe there is something we can do to mark the corresponding sections as "KEEP" like you've done in the linker script. Also, I can change the default linker script to include these KEEP expressions. Can you tell me exactly which sections you surrounded with KEEP()?

    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:
    .ctors*
    .dtors*
    
    FFDS1 required a bit more:
    .FFDS1.cog (see above for this one)
    .boot
    .lmmkernel
    .init*
    .data
    .ctors*
    .dtors*
    
    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?
  • David BetzDavid Betz Posts: 14,516
    edited 2012-12-28 20:20
    SRLM wrote: »
    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:
    .ctors*
    .dtors*
    
    FFDS1 required a bit more:
    .FFDS1.cog (see above for this one)
    .boot
    .lmmkernel
    .init*
    .data
    .ctors*
    .dtors*
    
    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?
    Okay, I'll wait for you to sort this out before making any changes. Let me know if you figure out that GAS symbol issue. By the way, does this only work with C++ code or should it work for plain C as well?
  • Heater.Heater. Posts: 21,230
    edited 2012-12-29 00:07
    ersmith,
    David's suggestion of using a library with each function in its own .o is a good one (and the usual approach for C).
    I have heard this a lot and always wondered why this solution is not considered an awful bad work around for the problem.

    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"?
  • David BetzDavid Betz Posts: 14,516
    edited 2012-12-29 06:02
    Heater. wrote: »
    ersmith,

    I have heard this a lot and always wondered why this solution is not considered an awful bad work around for the problem.

    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"?
    I agree with you. I had just forgotten about gc-sections. I guess we should try to get that to work.
  • CircuitsoftCircuitsoft Posts: 1,166
    edited 2012-12-29 11:22
    When I've used -ffunction-sections and --gc-sections before, on ARM, I had this issue. What I wound up doing is going into the Linker script, and changing the "Startup" parameter:
    ENTRY(__vectors)
    

    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.
Sign In or Register to comment.