zig icon indicating copy to clipboard operation
zig copied to clipboard

Proposal: Add linkname in addition to linksection

Open ikskuh opened this issue 1 year ago • 8 comments

Right now, exported and imported symbols in Zig use verbatim the name that is given when using export fn/extern fn or use a much more complex scheme when using @export/@extern.

Problem

So if i want to export (or import) a function with another name than i want to have it in Zig, i have two options:

// import
extern fn SDL_CreateWindow(…) …;
pub const createWindow = SDL_CreateWindow;

// export
export fn SDL_CreateWindow(…) … {}
pub const createWindow = SDL_CreateWindow;

or

// import
pub const createWindow = @extern(
    *const fn(…) callconv(.C) …,
    .{ .name = "SDL_CreateWindow" },
});

// export
pub fn createWindow() callconv(.) … { … }
comptime {
    @export(
        createWindow,
        .{ .name = "SDL_CreateWindow" },
    );
}

Both options feel clunky when i only want to change the name of the emitted symbol, so a C library will have a nice prefixed variant that doesn't clash names, and the Zig variant feels way more ziggy.

Proposal

Thus, i propose introduction of a new keyword named linkname which is valid whereever linksection is valid which changes the way a function symbol is emitted internally:

// import
pub extern fn createWindow(…) linkname("SDL_CreateWindow") …;

// export
pub export fn createWindow(…) linkname("SDL_CreateWindow") … { }

Use Case

Ashet OS

Ashet OS uses dynamic linking as the primary kernel interface, and i keep my symbols nicely namespaced and grouped:

ashet.kernel.process.debug.write_log

which sadly leads to Zig code looking likes this:

pub extern fn @"ashet.kernel.process.debug.write_log"(…) void;

which doesn't really read well, and renaming all symbols manually is tedious and error prone

C Library Wrappers

Using wrappers in Zig is common and having a simple wrapper that would export SDL functions without SDL_ prefix would make the code much nicer to read:

- const window = sdl.SDL_CreateWindow(…);
+ const window = sdl.CreateWindow(…);

apigen

apigen will gain support for something like global_prefix SDL_ which adds this prefix to all exported/imported symbols.

This would allow making nice generated Zig "Headers" which do not expose this prefix on the API surface in Zig, making the usage and the reading/writing way more convenient.

ikskuh avatar May 19 '24 09:05 ikskuh

Additional potential drive-by proposal by myself:

Replace extern "libname" fn func () T with extern fn func() linklibrary("libname") T, the extern "libname" always felt out-of-language

ikskuh avatar May 19 '24 12:05 ikskuh

Replace extern "libname" fn func () T with extern fn func() linklibrary("libname") T, the extern "libname" always felt out-of-language

Could you make that a separate, easy to accept, proposal? That syntax often gets confused with C++'s extern "C".

Vexu avatar May 19 '24 13:05 Vexu

Replace extern "libname" fn func () T with extern fn func() linklibrary("libname") T, the extern "libname" always felt out-of-language

Could you make that a separate, easy to accept, proposal? That syntax often gets confused with C++'s extern "C".

sure!

ikskuh avatar May 19 '24 19:05 ikskuh

What does it boil down to on Windows? What will the linker see?

EDIT: actually, I should probably expand the question to "what happen on each platform". Do we get two exports, one being an alias for the other? Or is the primary definition a local, while linkname emits the global name? Or in MachO lingo, is the primary a private external visible only to the linkage unit?

kubkon avatar May 19 '24 20:05 kubkon

What does it boil down to on (whatever)? What will the linker see?

Basically what you do is that you have a Zig symbol name and a linker symbol name. Right now, these are the same unless @export is used. The linker will see whatever is written in linkname() while the programmer sees the other name.

ikskuh avatar May 19 '24 21:05 ikskuh

What does it boil down to on (whatever)? What will the linker see?

Basically what you do is that you have a Zig symbol name and a linker symbol name. Right now, these are the same unless @export is used.

The linker will see whatever is written in linkname() while the programmer sees the other name.

Nice, ok, so there's no aliasing. In that case I am in favour of this change.

kubkon avatar May 19 '24 21:05 kubkon

I'm assuming this is similar to how Hare does linking?

export @symbol("glfwInit") fn init() void; 

which would link the init function to the glfwInit symbol from GLFW and expose it for use, similarly with this proposal

pub extern fn init() linkname("glfwInit") void;

would do the same, I imagine? I feel like this syntax would be a lot easier to understand than the status-quo.

hqnna avatar May 20 '24 05:05 hqnna

I'm assuming this is similar to how Hare does linking? </snip> would do the same, I imagine? I feel like this syntax would be a lot easier to understand than the status-quo.

Yep! Exactly!

ikskuh avatar May 20 '24 07:05 ikskuh

In AshetOS it would be very nice to have this feature:

pub const syscalls = struct {
    /// All syscalls related to generic resource management.
    pub const resources = struct {
        /// Returns the type of the system resource.
        pub const get_type = @extern(*const fn (
            SystemResource,
        ) SystemResource.Type, .{ .name = "ashet_syscalls_resources_get_type" });

        /// Returns the current owner of this resource.
        pub const get_owners = @extern(*const fn (
            SystemResource,
            owners_ptr: ?[*]Process,
            owners_len: usize,
        ) usize, .{ .name = "ashet_syscalls_resources_get_owners" });

        /// Adds the process to the owners of this resource, so the process
        pub const send_to_process = @extern(*const fn (
            SystemResource,
            Process,
        ) void, .{ .name = "ashet_syscalls_resources_send_to_process" });

        /// Drops the ownership of the resource for the current process.
        pub const release = @extern(*const fn (
            SystemResource,
        ) void, .{ .name = "ashet_syscalls_resources_release" });
    };
};

It makes the code hard to read using @extern for basically all functions on the system boundary

ikskuh avatar Jun 11 '24 20:06 ikskuh

the llvm backend in the compiler would also a prime benefactor from this pattern https://github.com/ziglang/zig/blob/0.13.0/src/codegen/llvm/bindings.zig#L27

nektro avatar Jun 11 '24 21:06 nektro

I think it would be nice to consider if this proposal could somehow be made to subsume #9575.

alexrp avatar Jun 17 '24 04:06 alexrp