Gone are the days of cherry picking the Standard Peripheral Library!
This tool has been largely inspired by @XaviDCR92's sdccrm tool and aims to provide a couple more improvements. Most imporatantly, the tool:
--opt-irq
is provided)Due to sdccrm's deprecated status and my personal preference for Python over C for pattern matching and text processing, I chose to develop the tool completely from scratch instead of forking sdccrm.
This tool has not been tested with debug information enabled. Enabling debug information will likely optimize significantly less code and probably mess with debug symbols.
Please note that this tool is still in early development and has not been thoroughly tested yet. So far, I have only tested it on numerous of my own projects where the tool delivers the expected results. That being said, I may have overlooked certain edge cases which could in the very worst case lead to the tool removing code that should not be removed. Hence, proceed with caution!
The tool has been written in Python 3 and is available on PyPI. You can install it using pip:
$ pip install stm8dce
Alternatively, you can clone the repository, and install it from the source:
$ git clone https://github.com/CTXz/STM8-DCE.git
$ cd STM8-DCE
$ pip install .
usage: stm8dce [-h] -o OUTPUT [-e ENTRY] [-xf EXCLUDE_FUNCTION [EXCLUDE_FUNCTION ...]] [-xc EXCLUDE_CONSTANT [EXCLUDE_CONSTANT ...]] [--codeseg CODESEG] [--constseg CONSTSEG] [-v] [-d] [--version] [--opt-irq]
input [input ...]
STM8 SDCC dead code elimination tool
positional arguments:
input ASM, rel and lib files
options:
-h, --help show this help message and exit
-o OUTPUT, --output OUTPUT
Output directory to store processed ASM files
-e ENTRY, --entry ENTRY
Entry label
-xf EXCLUDE_FUNCTION [EXCLUDE_FUNCTION ...], --exclude-function EXCLUDE_FUNCTION [EXCLUDE_FUNCTION ...]
Exclude functions
-xc EXCLUDE_CONSTANT [EXCLUDE_CONSTANT ...], --exclude-constant EXCLUDE_CONSTANT [EXCLUDE_CONSTANT ...]
Exclude interrupt handlers
--codeseg CODESEG Code segment name (default: CODE)
--constseg CONSTSEG Constant segment name (default: CONST)
-v, --verbose Verbose output
-d, --debug Debug output
--version show program's version number and exit
--opt-irq Remove unused IRQ handlers (Caution: Removes iret's for unused interrupts!)
Example: stm8dce file1.asm file2.asm file3.rel file4.lib ... -o output/
The tool receives a list of SDCC generated assembly-, lib- and rel files for the STM8 as input and outputs the optimized assembly files to the specified output directory.
The .lib
and .rel
files aren't processed, but are necessary for stm8dce to ensure that functions used by these files aren't accidentally removed (See "Adding .rel and .lib Files"). Additionally the --opt-irq
flag may be provided to also eliminate unused interrupt handlers.
Note: Optimizing away interrupt handlers will strip away their default behaivour of returning from the interrupt. This means, if a unused interrupt handler is accidentally triggered, the STM8 will likely crash. Use this feature with caution and ensure that only handled interrupts are enabled in your firmware!
For a practical demonstration, check out the example directory in this repository. It features a straightforward Test project designed for the STM8S103, complete with a Makefile and a comprehensive README that walks you through the entire process: from compiling your project into assembly files, to optimizing them with stm8dce
, and finally assembling and linking them together into an elf and ihx file. The project also includes all STM8S103-compatible modules from the SPL to really showcase the tools capability. Without DCE, incorporating all modules would quickly surpass the STM8S103's flash memory capacity.
For a quick demonstration however, the following examples will suffice:
Suppose SDCC generated us a couple of assembly files for our STM8 project:
main.asm stm8s_it.asm stm8s_gpio.asm
To perform dead code elimination on these files, we can simply run:
$ mkdir output
$ stm8dce -o output main.asm stm8s_it.asm stm8s_gpio.asm
This will output the optimized assembly files to the output
directory.
When using precompiled libraries or object files in your project, these files may reference functions defined in your project's assembly files. In such cases, you should provide these .rel
and .lib
files to the tool. While the tool does not process these files, it ensures that functions used by them are not mistakenly removed.
For example, if your code uses the printf
function from the standard library, you must provide the standard library to the tool. Otherwise, it might remove the putchar
function, which is required by printf
, leading to a linker error:
?ASlink-Warning-Undefined Global '_putchar' referenced by module 'puts'
To prevent this, include the standard library when running the tool:
mkdir output
stm8dce -o output main.asm stm8s_it.asm stm8s_gpio.asm path/to/stm8.lib
Typically, the standard library is located in the lib
directory of your SDCC installation. On Linux systems, this is usually found at /usr/share/sdcc/lib/stm8/stm8.lib
or /usr/local/share/sdcc/lib/stm8/stm8.lib
.
Similarly, you can provide .rel
files to the tool:
stm8dce -o output main.asm stm8s_it.asm some.rel some_other.rel
Lets assume we want to optimize the same files as before, but also eliminate unused interrupt handlers:
$ mkdir output
$ stm8dce --opt-irq -o output main.asm stm8s_it.asm stm8s_gpio.asm
This should result in a slightly reduced code size (unless all interrupts are utilized, which is highly improbable), as unused interrupt handlers are eliminated.
However, this optimization comes with a minor "drawback": the tool removes the default iret
instruction from unused interrupt handlers. Consequently, if an unused interrupt handler is accidentally triggered, the STM8 may crash. Use this feature with caution and ensure that only handled interrupts are enabled in your firmware!
By default, the tool will assume that the entry label is _main
. If your entry label is different, you can specify it with the -e
flag:
$ stm8dce -e _my_entry_label -o output main.asm stm8s_it.asm stm8s_gpio.asm
If you want to exclude certain functions or constants from being optimized away, you can do so by providing the -xf <function>
or -xc <constant>
flags. Note that these must be provided as they are named in the assembly files (i.e. they contain a leading underscore: hello_world
-> _hello_world
). This may be useful if you want to keep certain functions or constants for debugging purposes, or if the tool does end up removing code that should not be removed. If the latter is the case, please don't hesitate to open an issue on this repository!
$ stm8dce -xf _my_debug_function1 _my_debug_function2 -xc _MY_DEBUG_CONSTANT1 _MY_DEBUG_CONSTANT2 -o output main.asm stm8s_it.asm stm8s_gpio.asm
In certain cases, there may be multiple local/static functions or constants with the same name in different files. If that is the case, the previous example will generate an error. In this case, you can specify the file name of the function or constant label as well:
$ stm8dce -xf main.asm:_my_debug_function -xc main.asm:_MY_DEBUG_CONSTANT -o output main.asm stm8s_it.asm stm8s_gpio.asm
If you want to see which functions and constants have been optimized away, you can provide the -v
flag:
$ stm8dce -v -o output main.asm stm8s_it.asm stm8s_gpio.asm
Removing Functions:
_unused_function - main.asm:123
_another_unused_function - main.asm:456
...
Removing Constants:
_UNUSED_CONSTANT - main.asm:3
_ANOTHER_UNUSED_CONSTANT - main.asm:4
...
Debug output can be activated using the -d
flag, offering verbose output and showcasing the tool's intermediate steps. Note that the output will quickly flood your terminal and is best redirected to a log file instead. Debug output primarily serves developers who wish to contribute to the project. It can also be useful for resolving issues with the tool.
$ stm8dce -d -o output main.asm stm8s_it.asm stm8s_gpio.asm > debug.log
@XaviDCR92's sdccrm tool, which this project took inspiration from, has been deprecated in favor of using their GNU Assembly-compatible SDCC fork for the STM8 along with their stm8-binutils fork to perform linking-time dead code elimination. I found that approach to sound good in theory, but in practice, the implementation comes with numerous issues, such as being incompatible with newer versions of SDCC, and the fact that SDCC's standard library has to be compiled manually if one requires it. Even once the standard library has been compiled, it uses platform-independent C code, which is not as optimized as the platform- specific assembly functions tailored for the STM8. I believe all of this mostly boils down to the fact that the SDCC fork's changes are not eligible for merging into the main SDCC repository, making it difficult to maintain and somewhat akward to use.
This section is still work in progress but will hopefully provide a brief overview of how the tool works. For the time being, take a peek at the source code. I've tried my best to add sufficient comments!
If you encounter any issues with the tool, please don't hesitate to open an issue on this repository. If you have any suggestions or feature requests, feel free to open an issue as well!
If you want to contribute to the project, feel free to fork the repository and open a pull request. I'm happy to review any contributions!