Thumb Mode Optimisation

Hi, I am looking for any proper working documentation on thumb mode Optimisation on arm architecture
I tried few ways, but there is no size reduction in generated binary file

[target.armv7-unknown-linux-gnueabi]
rustflags = ["-C", "target-feature=+thumb-mode"]

Here is a related post, maybe can help: Rust Size in embedded syatem for ARM CR4 - help - The Rust Programming Language Forum

thumb mode can be enabled in a per-function basis, using the #[instruction_set(arm::t32)]` attribute, like this:

#[instruction_set(arm::t32)]
fn foo() {
    //...
}

see rfc2867 for details.

for certain cross-compiled bare metal architectures, there's different target triples for a32 and t32 codegens, such as armv4t/thumbv4t or armv5te/thumbv5te, but I don't think armv7-linux has a corresponding counter part.

There are a lot of functions in the project, this approach would make it difficult to apply on per function basis, is there any other method to build project for entire project

from my understanding, the code gen flags in OP -C target-features=+thumb-mode should enable the thumb-mode attribute by default.

can you show some relevant contents of the symbol table please?

$ readelf --syms target/armv7-unknown-linux-gnueabi/debug/BINARY_NAME

It is too big to display here, btw does arm-unknown-linux-gnueabi support thumb mode Optimisation

you can check it yourself. here's an example:

File: target/arm-unknown-linux-gnueabi/debug/libfoobar.rlib(foobar-7b0841136d790d74.11n7uttcboidvawvgdtli2idt.rcgu.o)

Symbol table '.symtab' contains 21 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS 11n7uttcboidvawv[...]
     2: 00000000     0 SECTION LOCAL  DEFAULT    3 .text._ZN6foobar[...]
     3: 00000000     0 NOTYPE  LOCAL  DEFAULT    3 $t
     4: 00000028     0 NOTYPE  LOCAL  DEFAULT    3 $d
     5: 00000000    16 OBJECT  LOCAL  DEFAULT   12 .L__unnamed_1
     6: 00000000     0 SECTION LOCAL  DEFAULT    7 .text._ZN6foobar[...]
     7: 00000000     0 NOTYPE  LOCAL  DEFAULT    7 $a
     8: 0000004c     0 NOTYPE  LOCAL  DEFAULT    7 $d
     9: 00000000    16 OBJECT  LOCAL  DEFAULT   14 .L__unnamed_2
    10: 00000000     0 SECTION LOCAL  DEFAULT   11 .rodata..L__unnamed_3
    11: 00000000     0 SECTION LOCAL  DEFAULT   16 .debug_abbrev
    12: 00000000     0 SECTION LOCAL  DEFAULT   17 .debug_info
    13: 00000000     0 SECTION LOCAL  DEFAULT   21 .debug_ranges
    14: 00000000     0 SECTION LOCAL  DEFAULT   23 .debug_str
    15: 00000000     0 SECTION LOCAL  DEFAULT   27 .debug_frame
    16: 00000000     0 SECTION LOCAL  DEFAULT   29 .debug_line
    17: 00000001    44 FUNC    GLOBAL DEFAULT    3 _ZN6foobar3foo17[...]
    18: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND _ZN4core9panicki[...]
    19: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND __aeabi_unwind_c[...]
    20: 00000000    80 FUNC    GLOBAL DEFAULT    7 _ZN6foobar3bar17[...]

the thing to look at is the special symbol $t (#3 in this example) and/or the lsb of some symbols' address (note the value of #17 is 00000001 in this example).

if you see a lot of $t mappings and the function symbols have odd addresses, they you already have them compiled in thumb-mode.


some explanation:

  1. per the arm elf spec, the special mapping symbol $a, $t, and $d point to arm-mode code, thumb-mode code, and data, respectively. these symbols are called "mapping symbols" if you want to lookup the terminology. (there are another kind of special symbols called "tagging symbols", but they are not present in this example)

  2. for relocatable ELF files, the lsb of the address of a thumb-mode function is set to 1, or in other words, the address appears as an odd number, while arm-mode functions have even addresses. the linker uses this bit of information to do relocation calculation.

  3. my example is a static library, the symbols are not resolved by the linker, so the values are all zero placeholders (apart from the lsb for thumb-mode functions)

Thank you for the detailed explanation and example, in my case it there is no single $t symbol,

So thumb mode is not enabled

However I tried this approach

export RUSTFLAGS="-C target-cpu=generic -C target-feature=+thumb" 

But I was getting thumb mode not supported for this architecture

The architecture I am using is arm-unknown-linux-gnueabi

is this a typo here? it should be -C target-feature=+thumb-mode

Yes, you are right, thumb-mode it is

I tried out with a sample app, where I have changed. .cargo/config with the target
armv7-unknown-linux-gnueabi
linker="arm-linux-gnueabi-gcc"
rustflags= [-C, target-feature=+thumb-mode]

After doing readelf on binary file
It gave a couple of $t symbols as you mentioned, apart from that, there were also $t.1, $t.2, ..... $t.n(n being some number)
Are these also thumb mode Optimised

One more query before and after this change, the size of binary file remained same, I checked with du -sh target/armv7-unknown-linux-gnueabi/debug/BINARY

Even with size <above_path >

Before it was
Text: 575882
Data: 12396
Bss: 172
Dec: 588450

After it was
Text: 485438
Data: 12396
Bss:172
Dec:498006

yes, these are all mapping symbols for thumb-mode code, see the ARM ELF spec for details.

you are checking a unstripped ELF file of a debug build, where most space of the file is taken up by debug information. changing code gen to thumb-mode only reduces the code size, which is insignificant in unstripped ELF file built in debug mode.

I would say a 15% code size reduction is not too bad, considering it is a debug build and no optimization is done.

if your end goal is to reduce the size of the executable file, changing to thumb-mode code gen can help, but it's not the most important step you should take. setting the proper optimization flags and stripping the debug information is much more important.

for example, try to add the following settings to your release profile and build in release mode, you'll see significant file size reduction.

# Cargo.toml
[proifle.release]
opt-level = "s"
strip = true

side note:

if you are still unsatisfied with the "typical" opt-level of "s", you can try "z", which is supposed to reduce the size even more (in theory, at least).

if it's still "too large", you'll have to use some "unconventional" tricks to reduce the size even more (but in practice, these "extreme" steps should be completely unnecessary for most use cases). see:

1 Like

Does the readelf --syms give $t symbols for release mode aa well?

if you stripped the binary, the symbols are definitely gone. but even if you didn't strip, I think the answer is still no, but I'm not sure. but again, I think whether a function is thumb-mode or not shouldn't matter very much. when it comes to optimization, always measure, never guess.

I have checked for release mode, there is no $t, I just wanted to confirm thumb mode is enabled or not

these mapping symbols are meant to be used by the linker and have local scope. so they should be present in the object files and/or library files, but have no use in the final executable file produced by the linker. if you want check them, you should look in the object files or libaries files, not the executable binary.

but the mapping symbols are not necessary anyway. if the symbols are not stripped, you can simply check the LSB of the address of functions. remember, symbols of thumb-mode functions have LSB set to "1", or, the address appears as odd numbers.

if the symbols are stripped, then I don't know how to check for thumb-mode easily. maybe try a disassembler?