itanium-cxx-abi / cxx-abi

C++ ABI Summary
504 stars 94 forks source link

Emergency EH buffer is overspecified #151

Open jwakely opened 2 years ago

jwakely commented 2 years ago

The description of the emergency buffer in 3.3.1 Exception Storage seems rather overspecified.

Recommendations:

fweimer-rh commented 2 years ago

I think the ABI should support a compilation mode in which an exception can always be thrown, translated to std::bad_alloc (or more likely, a subclass) if memory allocation fails. Currently, __cxa_allocate_exception is declared noexcept in our headers.

Maybe we can even use a tagged pointer to preserve some information about the exception that had to be translated (like its type_info). In most cases, it will be a memory allocation failure that is being suppressed, so it's not likely that there is much actionable information lost anyway. As a shared resource, memory allocation failures are not properly encapsulated, so the reported failure may not even point to the right subsystem.

Once we have a non-terminating implementation, I wonder if we still need the emergency pool.

jwakely commented 2 years ago

It would be nice if there was some allowance for non-unique exceptions. If the runtime created a single std::bad_alloc (or subclass, possibly in static storage) on program startup then it could just rethrow that same object every time allocation fails (increasing the ref count on the shared object). But I think that requires a language change, so out of scope for the ABI.

rjmccall commented 2 years ago

I agree that this seems weirdly over-specified. The ABI document should only describe the high-level requirements of the interface, and I don't see why anything about the underlying allocation scheme needs to be part of that.

If we can't come up with a high-level requirement that implementations can reasonably live up to (e.g. that we can throw at least one emergency exception on every thread, which would require us to eagerly reserve that space in every thread allocation, which we probably don't want to do), then we shouldn't say anything normative at all. In that case, we should just make a strong recommendation that implementations have some limited ability to throw exceptions even when allocation fails.

rjmccall commented 2 years ago

It would be nice if there was some allowance for non-unique exceptions. If the runtime created a single std::bad_alloc (or subclass, possibly in static storage) on program startup then it could just rethrow that same object every time allocation fails (increasing the ref count on the shared object). But I think that requires a language change, so out of scope for the ABI.

It seems to be an open question whether this is actually ruled out. You can convincingly argue that distinct evaluations of one-operand throw have to produce different exceptions: [except.throw] says that there's an exception object which is initialized from the operand, and the generic object rules say that different objects have to have different addresses. However, I don't see anything in the library section that requires operator new to throw an exception exactly as if by evaluating a throw expression; it just has to throw something of type std::bad_alloc. And I don't see anything semantic about the std::bad_alloc interface which requires objects to be uniquely generated for each failed allocation, like a mutable member or specific information about the allocation request. And if exception objects aren't guaranteed to be unique (already true because of rethrowing), then programs aren't permitted in general to do corner-case stuff like re-using an exception object's memory temporarily (which I guess would otherwise be allowed as long as you put an object of the original type back in place before the unwind system inspects it again). So if all that holds up, in principle there's no reason not to throw a shared bad_alloc exception.

With that said, I don't think implementations should try this without getting the committee's blessing first.

jwakely commented 2 years ago

However, I don't see anything in the library section that requires operator new to throw an exception exactly as if by evaluating a throw expression;

Ah good point. I think we agree that an arbitrary throw std::bad_alloc(); in user code must throw a unique object, but operator new can do something different.

With that said, I don't think implementations should try this without getting the committee's blessing first.

A few of us on the committee have already been tossing the idea around. Maybe it's time to formalize it.

rjmccall commented 2 years ago

Sounds great.

I think we agree that an arbitrary throw std::bad_alloc(); in user code must throw a unique object,

Yeah, completely agreed there, absent some further permission from the standard.