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

Richard Smith richardsmith at google.com
Thu Oct 13 00:07:50 UTC 2016


On 12 October 2016 at 16:34, John McCall <rjmccall at apple.com> wrote:

>
> 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> wrote:
>
>> On Oct 12, 2016, at 11:58 AM, Richard Smith <richardsmith at google.com>
>> wrote:
>> On 11 October 2016 at 19:20, John McCall <rjmccall at apple.com> wrote:
>>
>>> On Oct 11, 2016, at 4:20 PM, Richard Smith <richardsmith at google.com>
>>> wrote:
>>> On 11 October 2016 at 15:17, John McCall <rjmccall at apple.com> wrote:
>>>
>>>> On Oct 11, 2016, at 2:11 PM, Richard Smith <richardsmith at google.com>
>>>> wrote:
>>>> Under
>>>>   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 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.
>

We definitely need different type_info for the qualified and unqualified
types, but I don't think that implies that the pointee type in the
type_info for corresponding function pointers must be different. GCC's
approach for transaction_safe is that the pointee type_info referenced by a
pointer type_info is always the function type *without* the
transaction_safe mangling, so the marking is present only on the pointer
type_info. That's definitely not ideal, but does seem to work.

That said, I like the end result of this approach a lot more:

> 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's definitely a pain for Clang targeting GNU OSs, since we support using
the installed version of libsupc++ as our ABI library. If the only fallout
were that you couldn't throw a noexcept function pointer and catch it as a
non-noexcept function pointer, that'd be fine, but link errors for the
__qualified_function_type_info vtable seem problematic.

We could potentially lazily emit that one symbol as a weak alias for the
__function_type_info vtable, but we tried something like that with sized
deallocation (which had largely similar issues) and found it to be more
trouble than it was worth.

> 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.
>

There's been some recent talk of permitting pointer-to-member-function
conversions that remove cvr-qualifiers and add ref-qualifiers; those two
may end up in the "trivially-convertible" camp for C++20.

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#1555 suggests
allowing implicit conversion between extern "C" function types and
non-extern-"C" function types in cases where the two calling conventions
are the same, as a way of resolving the issues leading to longstanding lack
of implementation of extern "C" function types by most vendors.

But I would certainly grant that it doesn't make sense to use these flags
to model, for instance, actually-different 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/531e37af/attachment-0001.html>


More information about the cxx-abi-dev mailing list