ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
32.03k stars 2.34k forks source link

Separate calling conventions by architecture #6057

Open ghost opened 3 years ago

ghost commented 3 years ago

The current std.builtin.CallingConvention is very monolithic and x86-specific. If we want to support all the myriad calling conventions of all our supported architectures, this will get messy quickly. We should have a more modular structure.

This could be as simple as pub const CallingConvention = switch (arch) // etc., but we'll probably want a solution that's more consolidated with other architecture-specific details once we consolidate architecture-specific details. We may even consider redefining CallingConvention like so:

pub const CallingConvention = fn (arch: builtin.Arch) type {
    return struct {
        temporary: []arch.Register,
        saved: []arch.Register,
        argument: []arch.Register,
        link_register: ?arch.Register,
        stack_pointer: ?arch.Register,
        global_pointer: ?arch.Register,
        thread_pointer: ?arch.Register,
        frame_pointer: ?arch.Register,
        result_location: ?arch.Register,

        /// We also define a mapping from ABI string names to calling conventions
        /// (this does not include compiler-fungible conventions -- those require a separate mechanism anyway)
        pub const read = fn (abi: []const u8) @This() {
            return switch (arch) {
                .arm => if (std.mem.eql(u8, abi, "eabi")) // ...
            };
        };
    };
};

This might also make it easier to implement custom backends without making invasive changes to the compiler.

ghost commented 3 years ago

I just had a thought: once #1717 comes through, extern const foo = fn ... will be distinct from const foo = extern fn ..., so manually specifying callconv(.abi) won't be necessary. Specifying .any and .coroutine are already unnecessary, since the compiler will automatically detect and apply these anyway, so that leaves .naked as the only cross-target callconv argument. Come to think of it, allowing extern on the value side (which we can do without ambiguity, as fn, union, struct, enum are all keywords), we don't need a callconv keyword at all, if we can handle that pesky .naked.

Perhaps a better solution would be to provide a keyword for naked functions, like the nakedcc of old, infer any other compiler-fungible convention, and specify well-defined conventions with arguments to extern. That's a separate proposal though.

ghost commented 3 years ago

The separate proposal is #7365.

kyle-github commented 3 years ago

As noted in Discord, what about architectures that do not have registers? Does anyone care?

While the example given in Discord was old (PDP-8) there are current systems that use such processors such as older versions of ARM GPUs and many TTAs. Others include the CPU in the TI-99 (TI-9900?) which sort of had registers, but they were just references to memory.

These systems are NOT common and it may be perfectly reasonable to not support them!