[cxx-abi-dev] manglings for exception specifications in function types

John McCall rjmccall at apple.com
Wed Oct 12 23:34:15 UTC 2016


> On Oct 12, 2016, at 2:09 PM, Richard Smith <richardsmith at google.com> wrote:
> 
> On 12 October 2016 at 13:51, John McCall <rjmccall at apple.com <mailto:rjmccall at apple.com>> wrote:
>> On Oct 12, 2016, at 11:58 AM, Richard Smith <richardsmith at google.com <mailto:richardsmith at google.com>> wrote:
>> On 11 October 2016 at 19:20, John McCall <rjmccall at apple.com <mailto:rjmccall at apple.com>> wrote:
>>> On Oct 11, 2016, at 4:20 PM, Richard Smith <richardsmith at google.com <mailto:richardsmith at google.com>> wrote:
>>> On 11 October 2016 at 15:17, John McCall <rjmccall at apple.com <mailto:rjmccall at apple.com>> wrote:
>>>> On Oct 11, 2016, at 2:11 PM, Richard Smith <richardsmith at google.com <mailto:richardsmith at google.com>> wrote:
>>>> Under
>>>>   http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0012r1.html <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0012r1.html>
>>>> 
>>>> the noexceptness of a function type is now part of the type. As a result, we need manglings for exception-specifications on function pointer/reference types:
>>>> 
>>>> void f(void()) {}
>>>> void f(void() noexcept) {} // ok, overload not redefinition
>>>> 
>>>> (It's not clear to me whether or not this was also necessary prior to C++17 to handle dependent exception specifications that appear lexically within the parameter list of a function template, and actual implementation practice varies as to whether such exception specifications are SFINAEable.)
>>>> 
>>>> 
>>>> In order to handle overloading/SFINAE on exception specifications in dependent cases, we need to be able to mangle not only "noexcept", but also "noexcept(expression)" and "throw(<types>)". Suggestion for manglings:
>>>> 
>>>> <exception-spec> ::=
>>>>   nx  -- non-throwing exception specification
>>>>   nX <expression> E  -- computed (value-dependent) noexcept
>>>>   tw <type>* E  -- throw (types)
>>>> 
>>>> <function-type> ::= [<CV-qualifiers>] [<exception-spec>] [Dx] F [Y] <bare-function-type> [<ref-qualifier>] E
>>>> 
>>>> In the case of throw(a, b, c), we could omit types that are neither instantiation-dependent nor pack expansions (if that omits all types, we can use the 'nx' mangling instead), since C++17 says you can't overload on the actual types in the dynamic exception specification, and we otherwise only need them to be present if they might result in a substitution failure.
>>>> 
>>>> Thoughts?
>>> 
>>> I think this is an amazingly late change to the language with pretty thin justification; does that count?
>>> 
>>> This really is a major change which can reasonably be expected to cause substantial source and binary breakage.  The proposal mentions transaction_safe as a feature that added similar complexity, but that analogy is weak because (1) TM is expected to be an optional TS, whereas noexcept is a mandatory core language feature, and (2) existing code does not use the transaction_safe attribute, whereas noexcept and throw() have seen widespread adoption, in the latter case for years.
>>> 
>>> If it is a goal of this proposal to eliminate the underspecified fake type system around exception specifications, it is worth noting that it completely fails to do so, since the checking rules for direct function pointer assignments are still quite a bit stronger than those provided by the new type system.
>>> 
>>> That was indeed a goal here. Can you expand on how it fails? Ignoring the (deprecated) dynamic exception specifications, this new approach seems stronger than the old type system, since it works for function types being arbitrarily nested within other types, not just one level deep within function types and pointers.
>> 
>> Are there any implementations which actually plan to throw out the dynamic exception specification matching logic?
>> 
>> *shrug* Maybe MSVC? Any conforming C++17 implementation will need to demote that side of their enforcement to a warning. And I think there are NB comments for C++17 proposing that we apply http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0003r4.html <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0003r4.html> for C++17 rather than waiting for C++20.
> 
> Not enforcing the old rules is also compatibility-breaking, of course, because of SFINAE.
> 
>>> Hmm, I thought we had added a rule to allow B to be deduced in
>>> 
>>>   template<class R, class ...A, bool B> void f(R(A...) noexcept(B));
>>> 
>>> but it looks like we actually didn't. =(
>> 
>> Hmm, that would work pretty well for this case.
>> 
>>> Yes, the above is a problem, if noexcept function types start to appear in existing code (for instance through use of decltype or by code that passes around noexcept function pointers).
>> 
>> Well, recall that noexcept function types have always been writable; they just didn't necessarily get enforced reliably.  Also, noexcept and throw() are pretty popular, and aren't there proposals to infer them in more cases?
>> 
>> Proposals, yes, but nothing in C++17.
> 
> I think it's reasonable to anticipate that when judging how often functions will be noexcept.
>  
>> It's really hard to say abstractly how much impact this will have.  There's a lot of potential for breakage, but it's also quite possible that there won't be many changes and that almost all of them will be lost in the great grey expanse of C++ binary compatibility.
>> 
>> We'll have an implementation soon, and then we can find out whether this is a problem in practice.
> 
> I'll admit that I don't attend committee meetings, but I thought that implementation experience was expected *prior* to standardization, not something that gets done months after voting the thing in concurrently with the committee finalizing the language in a draft for next year's release.
> 
> Some of us try to push for that. So far we've not had much success.

A pity.

Okay.  Doug Gregor has strong opinions about this, but I'll let him speak for himself if he wants; I'll drop my own objections to the feature pending implementation experience.

>>> You will note that I have omitted the necessary specializations for "transaction_safe", as well as the incredibly common extension of specialized calling conventions.
>>> 
>>> This also breaks source compatibility for template matching, and basically every function template in the standard library is going to change manglings (and become *much* larger) due to noexcept expressions now being mangled.
>>> 
>>> It's a problem, but I don't think it's as bad as you claim. The mangling of a function still wouldn't include its exception specification; this would only affect mangling in cases where a parameter or return type or template argument involves a function type with an exception-specification -- a lot less common than every function template in the standard library, but this still does change manglings for existing code.
>> 
>> Okay, so it only triggers SFINAE failures in nested function types, and you can't overload templates by it?  I agree that that helps a lot.
>>> And the entire proposal seems to have forgotten about reference-to-function types.
>>> 
>>> The change to [dcl.init.ref]p4 allows a reference to non-noexcept function to bind to a noexcept function, and this indirectly allows the same during overload resolution, casts, and so on. What additional considerations were missed?
>> 
>> I hadn't realized that the expression logic was so consistent about defining e.g. the behavior of the conditional operator on l-values in terms of reference binding.  I apologize.
>> 
>> ...I see that this adds a new special case to exception handling.
>> 
>> Yes; I'd forgotten to mention this side of the ABI change.
>> 
>> We'll also need a new flag on type_info objects to model this. In line with the transaction_safe changes that Jason proposed, I suggest adding a __noexcept_mask = 0x40 to __pbase_type_info, and representing a pointer to noexcept function as a pointer with __noexcept_mask bit set to the corresponding *non-noexcept* function pointer type.
> 
> I strongly disagree; we should take this opportunity to revisit that decision.  The floodgates are open, and this set of function type attributes is clearly going to grow over time.  (It's actually transaction_safe that really drives this point home; noexcept is at least a longstanding part of the core language in various forms.)  We also have a lot of vendor-specific function type attributes that are part of the type but just aren't standardized and can't be represented in type_info.  I don't think it makes sense to indefinitely keep hacking these things into the pointer type flags; we should just bite the bullet and create a new function_type_info subclass.

Oh, for what it's worth, this is actually mandatory: typeid does have to be able to return a type_info for an arbitrary type, not just a pointer or member pointer for it.  typeid does not perform function-to-pointer conversion on an expression operand, and it can also be used with an arbitrary type-id.

> OK. How about this:
> 
> class __qualified_function_type_info : public __function_type_info {
> public:
>   const __function_type_info *__base_type;
>   unsigned int __qualifiers;
>   enum __qualifiers_mask {
>     __const_mask = 0x01,
>     __volatile_mask = 0x02,
>     __restrict_mask = 0x04,
>     __lval_ref_mask = 0x08,
>     __rval_ref_mask = 0x10,
>     __noexcept_mask = 0x20,
>     __transaction_safe_mask = 0x40
>   };
> };
> 
> ... where __base_type is the unqualified function type, included to avoid the need for string comparisons when checking for a matching exception handler. The base class __function_type_info would be used for types with no qualifiers.

Representing the base type this way makes sense to me.

Adding a new class does have backwards deployment problems: normally these type_infos are emitted by the ABI library, but it won't have this class on older targets.  I don't think we want to lazily emit this class in every translation unit that uses it; that's a lot of code, including some that's intricately tied to the EH mechanism.  On Darwin, I don't have any problem with saying that we just don't support the extended information unless the minimum deployment target is an OS that provides the class; after all, the exceptions machinery won't support it without updates either.  Is a similar answer acceptable to other people, or do we need something more sophisticated?

> It might also be reasonable to reserve a bit for 'noreturn', since several compilers treat it as part of the function type in some way.

I'm not sure this is the right representation for function type qualifiers.  It seems to me that there are two kinds:
  - those that create a (trivially-convertible) subtype of the unqualified type, like 'unwind', 'noexcept', and (for the implementations that make this decision) 'noreturn', and
  - those that don't, like cvr-qualifiers, ref-qualifiers, and calling conventions.
EH matching needs to verify that the subtyping qualifiers of the source type are a superset of the subtyping qualifiers of the target type, and it needs to verify that the other qualifiers match exactly.

I don't think catch-matching on a qualified function pointer type is an operation we need to expend a lot of effort optimizing.  I would rather have a more general representation that allows fairly painless vendor extension; I think that calls for at least the ability to have *some* string-based matching, even if maybe the standard ones get allocated to bit-fields.

John.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://sourcerytools.com/pipermail/cxx-abi-dev/attachments/20161012/402c844e/attachment-0001.html>


More information about the cxx-abi-dev mailing list