Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use a builtin enum for calling conventions instead of keywords #661

Closed
andrewrk opened this issue Dec 19, 2017 · 17 comments · Fixed by #3977
Closed

use a builtin enum for calling conventions instead of keywords #661

andrewrk opened this issue Dec 19, 2017 · 17 comments · Fixed by #3977
Labels
accepted This proposal is planned. breaking Implementing this issue could cause existing code to no longer compile or have different behavior. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@andrewrk
Copy link
Member

andrewrk commented Dec 19, 2017

right now we have these keywords for function calling conventions:

  • extern - must have C ABI. Also used to indicate C ABI on structs, enums, and unions.
  • stdcallcc
  • coldcc - cold calling convention is a thing, but this might be better off as an optimizer hint that the function is cold rather than specifying a calling convention. Currently the line is blurred.
  • nakedcc - used for entry points like _start and when inline assembly defines the whole function.

If no calling convention is specified, then the calling convention is selected automatically (and currently defaults to the "fast calling convention" that LLVM has available.) Related issue: #105 when mixing automatic calling convention with specified calling conventions.
Any function can be exported, and if the calling convention of an exported function is not specified, then extern is chosen.

This proposal is to make a builtin enum:

pub const CC = enum {
    Auto,
    C,
    StdCall,
    Cold,
    Naked,
};

And instead of keywords, the fn keyword has optional calling convention specifier that matches align, like this:

export fn _start() callconv(builtin.CC.Naked) {
    // ...
}

If a calling convention is not specified, builtin.CC.Auto is the default. This is similar to the endianness in pointers proposal (see #649). When displaying a fn type, the calling convention only has to be specified if it is not builtin.CC.Auto.

Then it becomes easier to add reflection with @typeInfo(@typeOf(_start)). builtin.TypeInfo.CallingConvention should be pulled out into builtin.CallingConvention

Now that enum literals have arrived, this proposal is nicer:

export fn _start() callconv(.Naked) {
    // ...
}
@andrewrk andrewrk added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Dec 19, 2017
@andrewrk andrewrk added this to the 0.2.0 milestone Dec 19, 2017
@kyle-github
Copy link

I like this. Extra keywords can come back and bite you later.

Java has annotations. Nim has odd looking pragmas etc. It seems like a good idea to have some way to apply metadata/modifiers to things without cluttering up the keyword set.

I like where you are going with this.

@phase
Copy link
Contributor

phase commented Dec 20, 2017

I like where this is going, but I don't see what other uses an enum annotation has for functions other than the calling convention. If you're going to introduce the notion of "you can annotate functions with enums", it better have more than one use case.

I think this can be applicable to other function attributes, such as noreturn and always_inline. Having something that encapsulates all of these would be cleaner than having separate systems for each subcategory of attribute.

@tiehuis
Copy link
Member

tiehuis commented Dec 20, 2017

This was something I was wondering about a little while back so I generally like the proposal.

I think it'd be wise as @phase mentioned on looking at the __attribute__ system in GCC/LLVM and what specific things are exposed. This system is pretty flexible and custom compilers (for example embedded gcc forks) add extra attributes necessary for their custom architectures if needed. I don't expect us to copy verbatim or anything, but just to keep in mind the extensibility as an example.

https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes

We already expose some of these at the language level (such as align) and others aren't applicable to zig, (i.e. non-null). I can see more attribute like functionality wanted in the future, in which case we would need a system to handle many attributes.

@andrewrk
Copy link
Member Author

Extra keywords can come back and bite you later.

How so?

@kyle-github
Copy link

How so?

The more keywords you have the more you have to watch your variable names etc. If you have multiple namespaces (i.e. struct field names do not collide with other names), then it is not so bad. Translating C to C++ can be "fun" sometimes because of the reserved words.

@andrewrk
Copy link
Member Author

andrewrk commented Dec 20, 2017

In zig you can use @"keyword" to name any identifier that collides with a keyword. For example if you import an .h file that has a struct with a field called align (a zig keyword) then you can access it like this: foo.@"align"

andrewrk added a commit that referenced this issue Jan 23, 2018
 * docgen supports obj_err code kind for demonstrating
   errors without explicit test cases
 * add documentation for `extern enum`. See #367
 * remove coldcc keyword and add @setIsCold. See #661
 * add compile errors for non-extern struct, enum, unions
   in function signatures
 * add .h file generation for extern struct, enum, unions
@andrewrk andrewrk modified the milestones: 0.2.0, 0.3.0 Feb 28, 2018
andrewrk added a commit that referenced this issue Mar 21, 2018
 * instead of `async(allocator) call()`, now it is
   `async<allocator> call()`.
 * Fixes syntax ambiguity when leaving off the allocator
 * Fixes parse failure when call is a field access

This sets a precedent for using `<` to pass arguments
to a keyword. This will affect `enum`, `union`, and
`fn` (see #661)
@andrewrk andrewrk modified the milestones: 0.3.0, 0.4.0 Aug 10, 2018
@andrewrk andrewrk added the accepted This proposal is planned. label Aug 23, 2018
@hryx
Copy link
Contributor

hryx commented Nov 14, 2018

I'm into it. If you do it, I'd definitely support using this bracket notation over parentheses for struct/union modifiers too, for consistency and intuitiveness. It's cool that whatever is inside the brackets is a comptime expression.

Java has annotations. Nim has odd looking pragmas etc.

Just to add to that list for inspiration, Rust has attributes, which have some usability drawbacks in my opinion:

  • Many different forms. #thing vs. #!thing vs. #!thing(option) vs. #!thing(option = "ok")
  • Visual locality. A Rust attribute affects "whatever is on the next line" or "whatever I'm inside of". It's nice that these zig brackets could be glued to the keyword to which they relate.

Nitpick: this should probably be builtin.Cc instead of builtin.CC to match the casing style of acronyms-as-words (Os, Xml, etc.).

@andrewrk andrewrk added the breaking Implementing this issue could cause existing code to no longer compile or have different behavior. label Nov 15, 2018
@andrewrk andrewrk modified the milestones: 0.4.0, 0.5.0 Mar 20, 2019
@LemonBoy
Copy link
Contributor

LemonBoy commented May 6, 2019

A few sparse thoughts since I need to mark some functions to have the "aapcs" calling convention.

  • naked is not a calling convention, it merely asks LLVM not to emit any prologue/epilogue code.
  • The square brackets are quite ugly, what about extern cc("a string") fn __do_things () void { ... } ?
  • There are many more CCs than the one listed in the LLVM language reference, check out this file for an exhaustive list.

@justinbalexander
Copy link
Contributor

* `naked` is not a calling convention, it merely asks LLVM not to emit any prologue/epilogue code.

It's not a calling convention in the enum you linked to, but a calling convention does define caller vs callee saved registers, etc, which are handled in prologue/epilogue code, so it seems a reasonable stretch to me.

Semi-related:

Instead of marking interrupts as having the "interrupt" attribute like in C, it makes sense to me to include those in the calling convention enum as well. It does after all, define how the function will be called and what registers it is expected to save and restore (among other things). I'm working on this issue right now, and that is my ultimate goal that made me interested in this issue specifically.

I am also curious, why did you need to mark some functions as having that calling convention? What is your target? AAPCS is not the default for that target?

@LemonBoy
Copy link
Contributor

LemonBoy commented May 6, 2019

It's not a calling convention in the enum you linked to, but a calling convention does define caller vs callee saved registers, etc, which are handled in prologue/epilogue code, so it seems a reasonable stretch to me.

naked means the callee doesn't care about the CC, the caller is still very much interested in passing the arguments correctly.

I am also curious, why did you need to mark some functions as having that calling convention? What is your target? AAPCS is not the default for that target?

On hard-float targets all the __aeabi intrinsics that deal with floating-point values must use the soft-float ABI.

@andrewrk
Copy link
Member Author

I updated this proposal to take into account tiehuis's comment and the existing align(expr) syntax on functions.

@justinbalexander
Copy link
Contributor

So async still goes at the beginning like export, extern, and inline?

@justinbalexander
Copy link
Contributor

Does this mean extern is only valid in front of a function prototype with no body, indicating that it is going to be resolved at link time or can extern also mean that if the calling convention is callconv(builtin.CC.Auto) (or missing) then it becomes callconv(builtin.CC.C)?

@justinbalexander
Copy link
Contributor

Similarly, does async mean that specifying the calling convention is invalid?

@shawnl
Copy link
Contributor

shawnl commented Oct 3, 2019

For Vectors there is not just one calling convention. x86_64 has 3 calling conventions (128-bit, 256-bit, and 512-bit SIMD width) and ARM, which generally just using the NEON one, is adding two more: SVE (multiple widths too!) and Helium for embedded cpus.

On x86_64 and arm64 I think we should forbid exporting or calling with vector sizes that are bigger than the target architecture, but that doesn't take care of Helium.

@daurnimator
Copy link
Contributor

Why did this annotation end up after the arguments next to the return type rather than next to the fn keyword?
IMO it looks like some annotation that applies to the return type rather than the function definition.

@andrewrk
Copy link
Member Author

Why did this annotation end up after the arguments next to the return type rather than next to the fn keyword?
IMO it looks like some annotation that applies to the return type rather than the function definition.

I agree with this critique - it looks more awkward than if it was on the fn keyword. But the answer is the same reason the align attribute goes after the parameters: because comptime parameters are in scope for the calling convention expression.

naked means the callee doesn't care about the CC, the caller is still very much interested in passing the arguments correctly.

Sorry @LemonBoy for the late response, but the way it works is that callers are not allowed to call "nakedcc" functions; they must @ptrCast the function to have a different calling convention in order to call it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted This proposal is planned. breaking Implementing this issue could cause existing code to no longer compile or have different behavior. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants