Symbol aliases

This is a continuation of the closed topic at Pre-RFC: Defining function aliases .

An alias is a second name for a function or global variable that is defined in the same object file. Because it's a second name for the same memory, it does not have its own address, rather a pointer to it and to the other name must compare equal.

These are useful in certain low-level systems programming circumstances when you have to define or use an interface like IRQ tables or similar (for example, a toolchain for an OS-less machine that requires a function named "irq_sio4" to be the handler for that interrupt, and is space constrained, you can use aliases to emit the empty function once and alias all the other IRQ handlers to that one).

In my case, I'm defining an interface to be used by non-Rust code, which my rust program will load via dlopen. Part of my interface includes the stack probing symbol, which may go by different names on different platforms. I'd like to expose it in my interface under a consistent name. One apparent option is to write a wrapper function that calls __rust_probestack but this is incorrect because stack prober does not follow the ABI of a function, and wrapping it in a function breaks the expected ABI and causes real bugs.

Aliases are an important feature for low-level systems programming, despite the fact that they aren't standardized in C or C++. In practice, ELF, Mach-O and COFF all support symbol aliases, and all production C and C++ toolchains support it either in the compiler by language extensions or in the linker with linker scripts. LLVM supports it with the @newname = alias %type, %type* @origname syntax.

There's a related feature that I'm explicitly not asking for. In some object file formats it's possible for the alias name to have different properties (linkage, visibility, ...) than the original. Although useful I'd like to be conservative now, focus on what I know I need as a rust user and what we know all platforms support, and leave other that feature to a future enhancement request.

One possible implementation would be to place an alias("") attribute on a declaration to indicate that this declaration is an alias of the other symbol, and require that the other symbol be defined and have matching types.

#[alias("sum")]
#[no_mangle]
pub extern "C" fn alias_of_sum(a: i32, b: i32) -> i32;
1 Like

If I understand correctly link annotation is what You need:

#[link(name = "sum")]
pub extern "C" fn alias_of_sum(a: i32, b: i32) -> i32;

Thanks for the suggestion! I tried this:

#[no_mangle]
pub extern "C" fn sum(a: i32, b: i32) -> i32 {
  a + b
}

extern "C" {
  #[link(name = "sum")]
  fn alias_of_sum(a: i32, b: i32) -> i32;
}

fn main() {
    println!("{}", unsafe { alias_of_sum(1, 2) });
}

but it fails to link with the error "undefined symbol: alias_of_sum".

Does the link attribute have any effect on declarations? If not, this would be a better syntax than a new alias attribute.

#[link(name = ...)] is for specifying the library name. You want #[link_name = "sum"]. See also: https://doc.rust-lang.org/reference/attributes.html

1 Like

That does make the example code I pasted work, but doesn't solve the motivating problem. #[link(name = ...)] is a way of renaming the function so it has one name in the rust parser and a second name in the object file. The tool I'm asking for is a way to associate two names with a function in the object file. Your suggestion would work if I only needed to call alias_to_sum from other Rust code, but in the motivating problem I need a symbol with that name to be present in final binary to resolve symbols in a shared object. Given:

extern "Rust" {
  #[link_name = "object_file_name"]
  fn rust_friendly_name(a: i32, b: i32) -> i32;
}

the rust_friendly_name never makes it into LLVM. I need something close to:

#[no_mangle]
pub static wasmer_probestack: unsafe extern "C" fn() = __rust_probestack;

except that this defines a pointer-sized variable named wasmer_probestack that contains the address of __rust_probestack. Using an alias would remove that one layer of indirection.

The object file doesn't have a high-level concept of functions, it stores a stream of bytes in one of more sections (different object files are different, I'm describing ELF here). The symbol table is a list of <symbol name, symbol address> mappings, and we call that symbol address the "start" or "address" of the function. The idea of an alias is to add a second symbol name that shares the same symbol address. The only way to do that in LLVM is to emit an llvm::GlobalAlias to the IR.

For an example in C with GCC extensions:

int sum(int a, int b) { return a + b; }
__attribute__((alias("sum"))) int alias_of_sum(int a, int b);
int main(void) {}

compiled to an executable on linux becomes:

$ nm alias | grep sum
0000000000001125 T alias_of_sum
0000000000001125 T sum

This is <symbol address, symbol type, symbol name> showing the two functions sharing the same address. I think there's no way to get this from rustc today.

1 Like

Perhaps the compiler should allow multiple link_name attributes on the same function.

5 Likes

Would an existential type work? (Using nightly features)

const wasmer_probestack: impl Fn() = __rust_probestack;

No, function definition types (ty::FnDef) are zero sized. Also const doesn't result in any symbols in the resulting program, instead the value is copy pasted at every usage. Only static results in a symbol.

The link_name attribute sets the name of an imported symbol and has no effect for exported symbols (that I know of).

That sounds a lot like defining a static which I believe should work? Maybe it needs a #[used] annotation although it shouldn't.

#[no_mangle]
pub static alias_of_sum: extern "C" fn(a: i32, b: i32) -> i32 = sum;
1 Like

That will create a static containing a pointer. You will need to dereference it before being able to call it. Symbol aliases on the other hand point directly to the respective function/static.

Correct. #[export_name = "foo"] can be used to set the name of an exported symbol.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.