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

Specify how scoped enums interact with varargs #86

Open
jicama opened this issue Oct 14, 2019 · 5 comments
Open

Specify how scoped enums interact with varargs #86

jicama opened this issue Oct 14, 2019 · 5 comments

Comments

@jicama
Copy link
Contributor

jicama commented Oct 14, 2019

Discussion of scoped enums and varargs in CWG led to them being declared conditionally-supported with implementation-defined behavior. So we should agree on semantics here.

@rjmccall
Copy link
Collaborator

Is there a reason to treat them differently from an unscoped enum? Are there compilers that do anything different?

@zygoloid
Copy link
Contributor

Unscoped enums are passed as their promoted type, and can be retrieved thusly with va_arg. Scoped enums don't promote, so we would need to decide how to pass them (as the underlying type, or as the promoted underlying type?) and what types can validly be used with va_arg to retrieve them (the original enum type, its underlying type, and/or its promoted underlying type).

Does anyone still use have a macro implementation of va_arg that can't easily carry custom logic to deal with this case?

@rjmccall
Copy link
Collaborator

You're right that you'd have to pass the scoped enum type to va_arg to read the argument, but as far as I can tell that's always been valid. And a macro implementation of va_arg would presumably just read the value out of the va_list as a scoped_enum * and then bump up to the next slot, exactly like it would any other type.

I guess what I'm saying is that I'm surprised that people think that this hasn't been supported behavior for the last 10+ years. Was there a bug report or something?

@zygoloid
Copy link
Contributor

Well, consider enum class X : char {};. Presumably that would be passed as an int, because the psABI doesn't specify how to pass anything smaller. But now consider a simple va_arg macro:

#define va_arg(list, type) (*(type*)(list -= sizeof(type)))

That macro fails for X, because it will bump list an insufficient distance. GCC and Clang both support this anyway, but GCC at least does not support the corresponding case where you use char instead of X (Clang allows even that and makes it work).

If on the other hand we had enum X : char {}; instead of enum class X : char {}; then the rules are clear that X is promoted to int when passed through ..., and va_arg(list, X) has undefined behavior.

So we have at least a choice between treating scoped and unscoped enums the same (and requiring the promoted underlying type to be used in the va_arg), or treating them differently (and allowing -- and perhaps expecting -- the original type to be used in the va_arg for the scoped case). And the latter is the status quo in GCC and Clang.

@rjmccall
Copy link
Collaborator

rjmccall commented Jan 22, 2020

Well, psABIs definitely specify how to pass e.g. a one-byte struct, and if that struct is trivially copyable it's allowed with varargs, so it's not strictly true that psABIs don't specify how to pass anything smaller, even if it's not obvious that that would be the right rule to use for scoped enums.

That definition of va_arg doesn't work for similar reasons. It also doesn't correctly handle the partitioning of the argument area into aligned slots in general, although for a target with sizeof(int) == sizeof(void*) it might incidentally work out for at least standard types. It also walks the argument area in the wrong direction from the normal convention, although that's a fixable mistake. (It's actually quite awkward to fix: I think you'd have to do something awful like * (const T *) (*(const char (**)[(sizeof(T) + slot_size - 1) / slot_size * slot_size]) list)++)), and even that relies on types never being overaligned for a slot, and of course it has strict-aliasing problems.)

Anyway, I agree that we need to specify some sort of default rule for psABIs that are written purely in terms of C/Fortran/whatever. Note that that means specifying a rule for these types in general, not just a rule for variadic arguments. Since we have to lower them to concepts known to be handled by the psABI, I think we have two options: handle them as the underlying type or handle them as a struct containing a single field of the underlying type. The former seems like the more obvious choice.

I would suggest:

Let E be a scoped enumeration type with underlying type U. Unless the psABI provides specific rules for E, they are as follows:

  • An object of type E has the same representation and characteristics as an object of type U.
  • An argument of type E is passed as if it were converted to type U and as if the corresponding parameter (if any) was of type U.
  • A return value of type E is returned as if it were converted to type U and returned from a function with return type U.

If an argument of type U would be subject to the default argument promotions, the implementation must perform the appropriate promotion to an argument of type E even though the promotions do not formally apply. Note that programs are required to pass E to va_arg, not U, which may cause trouble for simple definitions of va_arg.

I don't think we've actually specified this lowering as this level of detail for any of the existing C++-specific types. For example, we give layout rules for member pointers, but not CC rules; and I'm not sure we ever specify that references have the same representation as pointers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants