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

Richard Smith richardsmith at google.com
Thu Oct 13 20:30:38 UTC 2016


On 13 October 2016 at 11:49, John McCall <rjmccall at apple.com> wrote:

> On Oct 13, 2016, at 11:29 AM, Richard Smith <richardsmith at google.com>
> wrote:
> On 13 October 2016 at 10:39, John McCall <rjmccall at apple.com> wrote:
>
>> On Oct 12, 2016, at 5:07 PM, Richard Smith <richardsmith at google.com>
>> wrote:
>> 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/p0
>>>>>> 012r1.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.
>>
>>
>> Ah, I think I see.  You're suggesting that the type_info for 'void(*)()
>> noexcept' could just be an attributed pointer to the type_info for
>> 'void()', but that the type_info for 'void() noexcept' would be some
>> different sort of thing.  I agree that this is possible, but it might paint
>> us into a corner; e.g. if the committee ever standardizes a richer
>> reflection system based on type_info, I would expect it to have some way to
>> ask for the pointee type of a pointer as a type_info, which we would then
>> be unable to implement correctly.
>>
>> 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.
>>
>>
>> I don't remember all the issues for sized deallocation, but at least some
>> of them wouldn't apply here.  IIRC, sized deallocation actually had to be a
>> weak alias to a local implementation in order to satisfy the ABI /
>> delegation rules, which wouldn't be true here.  More importantly, sized
>> deallocation required adding code to the majority of translation units,
>> which, uh, I don't expect we'll see similar problems from here.  Still,
>> this is asking a lot of of the linker/loader.
>>
>
> The large number of duplicate copies was a pain, but wasn't the reason we
> ended up turning that mechanism off. I don't exactly recall the details,
> but the gist was that we needed linker magic to force a user replacement
> version from the main binary to be exported so that DSOs would see it, but
> that mechanism had problematic interactions with weak definitions.
>
> We can avoid introducing the new class if we can find some other way to
>> encode in the object that it has extra fields.  Unfortunately, a
>> __function_type_info only has a v-table pointer (inviolate) and a name
>> pointer (just a const char*, i.e. 1-byte aligned).  We can't put anything
>> in the string itself because it has to be globally uniqued, and if our name
>> "wins" and is used in a different type_info which doesn't provide the extra
>> fields, we're in trouble.  So I don't think this is possible.
>>
>> But lazily emitting the v-table also creates nasty problems, chiefly
>> because it requires us to also emit the virtual function definitions
>> lazily, and those are pretty complex.
>>
>> Can we emit a weak definition in the compiler runtime library, or does
>> that have the same problems as a weak definition in every translation unit?
>>
>
> In our case, for some targets there simply is no compiler runtime library
> that we control. We might not even be involved in the link step.
>
> Perhaps we could use an ifunc selecting between the vtable of
> __qualified_function_type_info (if it's defined) and that of
> __function_type_info (otherwise) as the vptr of the type_info object for a
> qualified function type.
>
>
> If you're comfortable with that, that sounds reasonable to me.  The ifunc
> will be run eagerly during load, but of course it's only necessary in
> translation units that actually use a qualified type_info.
>
> 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.
>>
>>
>> Okay, I accept that the standardized attributes should just be
>> represented as specific bits and that baking subtyping into the
>> representation is fraught.  So maybe the representation should be:
>>
>> 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
>>   };
>>   const char *__extended_qualifiers;
>> };
>>
>> And the convertibility check requires the extended qualifiers to either
>> both be null or string-compare equal.
>>
>
> What does this buy us over keeping the extended qualifiers in the base
> type? Is the idea that a target-specific runtime could choose to allow more
> conversions based on extended qualifiers?
>
>
> That's a potential possibility.  Mostly we need *some* rule for what to
> use as the base type should be, and I was thinking that you wanted it to
> always be a totally-unqualified type.  Is it basically the function type
> minus anything we can represent in flags?
>

That's what I had in mind.


> John.
>
>
>
>> John.
>>
>>
>> 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/20161013/4e5c038b/attachment-0001.html>


More information about the cxx-abi-dev mailing list