Why the monomorphized code for the generic function is absent in compiled binary when using the release build?

If we give different types of data to a generic function the compiler compiles the function for each of the types, and saves it to compiled binary. From the target/debug folder using GNU Binary Utilities nm it can be seen that compiler compiled the function multiple times for each of the type it gave it to. However the from the target/release(after compiling with --release flag) folder no such code is seen. Here is my code:


fn main() 
{
    give(78);
    give(56.90);
    give("Catharine");
}

fn give<T>(t: T)
{
    let _ = t;
}

After running the command ~/sd/target/debug$ nm sd | grep "give", it returns the following:

0000000000007610 t _ZN2sd4give17h6a5ee4540907b9d6E
0000000000007620 t _ZN2sd4give17h946fe08c24ae76cdE
0000000000007640 t _ZN2sd4give17hf27cbfec194fe4aeE

Which is expected as I called the give function with three different data types.

Then I recompiled the project with --release flag.

Now running this command ~/sd/target/release$ nm sd | grep "give" it returns nothing. Here sd is the name of the project. It seems that relase build do not create the monomorphized code for function give.

Why?

Thanks.

Probably because it does nothing and is optimized out. Or that the function name is mangled.

7 Likes

If you make sure the function is not inlined, and e.g. call it with a type that needs to execute some destructor (so that the call actually does something, because calls to no-op functions can be removed even when inlining them is disabled presumably because the ABI of the function essentially just becomes "don't call anything"; similar to how e.g. unused function arguments can be optimized away etc…), you will get to see monomorphized give functions in your binary.

fn main() 
{
    give(Box::new(78));
    give(Box::new(56.90));
    give(Box::new("Catharine"));
}

#[inline(never)]
fn give<T>(t: T)
{
    let _ = t;
}
2 Likes

By the way, is it possible to make this sure at all, other than making the function extern? I remember the case where even inline(never) didn't help.

I don’t know the details here; I was rather addressing the more concrete action to “make sure” the function is not inlined by

  • putting the #[inline(never)] and also
  • looking into the resulting binary that the function call is actually present as expected

i.e. the part of checking the resulting binary is part of “making sure the function is not inlined” (which might of course be an observation that only applies to that particular compiler invocation), which is appropriate here since the goal was merely to help successfully demonstrate monomorphization in action in optimized code.

In general, it is impossible to guarantee anything about inlining. For example, LTO will happily inline anything at anytime, since nothing you could place in source code would remain in the assembly, which is processed by LTO. Similarly, a WASM JIT will also happily inline any function it sees fit.

But you can try asking the compiler nicely, and if you don't press too hard on optimizations, you may succeed. If you don't want something to be inlined, it is best to place it and its use at different codegen units, i.e. at different crates (a crate can span several codegen units, but those are impossible to control).

You certainly shouldn't rely on the lack of inlining for any kind of correctness.

5 Likes

Thanks. There are some new terms(inlining, ABI, no-op functions) for me in your post to learn.