Simple functions in rust in the release version do not appear in the symbol table

Create a rust crate use cargo new --lib zero_obj,And then write a very simple function。

pub fn test_function_base001() -> i32 {
    2
}

Compile the release version:

cargo build --release

Then look at the symbol table of the rlib file.

readelf -sW libzero_obj.rlib 

File: libzero_obj.rlib(lib.rmeta)

Symbol table '.symtab' contains 1 entry:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 

File: libzero_obj.rlib(zero_obj-68a8e959160a6c87.zero_obj.e381423afbee4894-cgu.0.rcgu.o)

Symbol table '.symtab' contains 3 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS zero_obj.e381423afbee4894-cgu.0
     2: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT    3 $d.0

I found that the test_function_base001 function I wrote was not in the symbol table, so where did my function go?
The function I defined is a pub function, which is a function that may be referenced by other crate, so where did it go?
When the output file is .so, this function is not available in .so.

[lib]
name = "zero_obj"
crate-type = ["dylib"]

I’d assume that is related to the fact that (since 1.75) small functions can be automatically marked #[inline].

This seems to be confirmed by a little testing I did which has

  • the function appearing in the table with cargo +1.74 build --release
  • the function disappearing again in the table with cargo +1.74 build --release when #[inline] is added
  • the function also appearing in the table with cargo +stable build --release or cargo +nightly build --release when #[inline(never)] is added

Oh, I see Automatically enable cross-crate inlining for small functions since 1.75.0 #. Thanks.

By the way, what kind of function is a small function?

1 Like

The definition of small function is (deliberately) unstable; it currently lives in the function that makes the decision about cross-crate inlining. At the time I looked, a small function is a leaf function (does not call other functions) with fewer than 100 statements in MIR, and that contains no "complex" code (complicated drop glue, asserts, panics, inline assembler etc), but this can change.

So, for example, the following dumb function is "small":

    fn small(&self, multiplier: i32) -> i32 {
        let multiplier = if multiplier > 32 { 32 } else { multiplier };
        self.value * multiplier
    }

It has no complex code, and it only drops an i32 at the end, so has no complicated drop glue or other complex code.

2 Likes