Skip to content

LTO failing to prune WASI fd_* imports #401

@ggoodman

Description

@ggoodman

In this repo, I've shown two scenarios where code that is functionally identical produces very different WASM binaries.

Setup

The two scenarios I'm testing are for a rust cdylib whose code looks like this:

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

#[export_name = "fallible_func"]
pub extern "C" fn fallible_func(val: i32) -> i32 {
    unsafe { Fallible_func(val) }
}

#[cfg(feature = "print")]
#[export_name = "print"]
pub extern "C" fn print(str: *const i8) {
    unsafe {
        // Call the FFI Print function with the a pointer to the C string `cstr`.
        Print(str as *mut i8);
    };
}

Importantly, the print function is guarded by the print feature that is off by default.

The c code we're binding looks like this:

#include <stdio.h>
#include <assert.h>

int Fallible_func(int value)
{
  assert(value > 0);
  return value;
}

void Print(char *str)
{
  printf("%s\n", str);
}

If we run the cargo build with NO_PRINTF=1, then the c code instead looks like this:

#include <stdio.h>
#include <assert.h>

int Fallible_func(int value)
{
  assert(value > 0);
  return value;
}

void Print(char *str)
{
  // printf("%s\n", str);
}

Scenario 1:

Note: This is all imports and exports.

  (import "wasi_snapshot_preview1" "fd_close" (func $__imported_wasi_snapshot_preview1_fd_close (;0;) (type 2)))
  (import "wasi_snapshot_preview1" "fd_fdstat_get" (func $__imported_wasi_snapshot_preview1_fd_fdstat_get (;1;) (type 3)))
  (import "wasi_snapshot_preview1" "fd_seek" (func $__imported_wasi_snapshot_preview1_fd_seek (;2;) (type 4)))
  (import "wasi_snapshot_preview1" "fd_write" (func $__imported_wasi_snapshot_preview1_fd_write (;3;) (type 5)))
  (export "memory" (memory 0))
  (export "fallible_func" (func $fallible_func.command_export))

WASM binary size: 2.4K

Scenario 2: NO_PRINTF=1

Note: This is all imports and exports. No imports at all here.

  (export "memory" (memory 0))
  (export "fallible_func" (func $fallible_func.command_export))

WASM binary size: 459B

Conclusion

This might be totally expected given the rust compilation pipeline and the way wasi-libc is authored and structured, but is quite surprising to me.

I was expecting that link-time optimizations would be 'smart' enough to notice that printf was not referenced by any exported functions in the rust library and recursively pruned. I've put together the minimal repro in hopes that it helps illustrate the observations in a reproducible way and could be helpful if this behaviour is not as intended.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions