Our template emission strategy is broken

Iain Buclaw via Digitalmars-d digitalmars-d at puremagic.com
Wed Nov 11 16:16:11 PST 2015


On 11 November 2015 at 19:02, Andrei Alexandrescu via Digitalmars-d <
digitalmars-d at puremagic.com> wrote:

> On 11/11/2015 12:19 PM, Iain Buclaw via Digitalmars-d wrote:
>
>> On 11 November 2015 at 17:25, Andrei Alexandrescu via Digitalmars-d
>> <digitalmars-d at puremagic.com <mailto:digitalmars-d at puremagic.com>> wrote:
>>
>>     On 11/11/2015 08:08 AM, David Nadlinger wrote:
>>     [snip]
>>
>>     Regarding the top-level issue. Walter and I agree it's an important
>>     problem, and also that plugging holes as they start leaking (which
>>     is what we've been doing so far) is not going to work well in the
>>     long haul.
>>
>>     A redesign of template instantiation is necessary, and Walter needs
>>     to be fully involved in it. However, please give it time. Walter is
>>     currently working full time on catching C++ exceptions from D code,
>>     and as we all know the best way of getting many things done is to do
>>     one thing at a given time and do it fully. It should take him at
>>     least two weeks' time to get there.
>>
>>
>> Please no.  I've done my fair share of investigating this in libunwind,
>> and it just isn't worth it.
>>
>
> Could you please provide Walter more detail? What's not worth it -
> catching C++ exceptions from D code? -- Andrei
>
>
The main problem for seamless support is having some way to generate the
C++ typeinfo in D to allow work across boundaries.  However there is
nothing stopping catch-all style exceptions from working.

Consider the following desired example (this works today with minimal
library changes in gdc's libunwind library):
*****************************
#include <iostream>

int compute(int a, int b)
{
  if (b == 0)
    throw "Division by 0 in C++!";

  return a / b;
}

void cpp_main()
{
  try {
     std::cout << compute(1, 0);
  } catch (...) {
    std::cout << "Unknown exception caught in C++, rethrowing..." << "\n";
    throw;
  }
}
*****************************

Then put in bindings and main in D.
*****************************
import std.stdio;

extern(C++) void cpp_main();

void main()
{
    try {
        cpp_main();
    }
    catch {
        writeln("Unknown exception caught in D, exiting...");
    }
}
*****************************

In our unwind library, we can put in a copy of the C++ unwind
implementation:
*****************************
static if (GNU_ARM_EABI_Unwinder)
{
  const _Unwind_Exception_Class __gxx_exception_class
  = ['G', 'N', 'U', 'C', 'C', '+', '+', '\0'];
}
else
{
  const _Unwind_Exception_Class __gxx_exception_class =
0x474e5543432b2b00UL;
}

// Structure of a C++ exception, represented as a C structure...
// See unwind-cxx.h for the full definition.
struct __cxa_exception
{
  void *exceptionType;
  void function(void *) exceptionDestructor;
  void function() unexpectedHandler;
  void function() terminateHandler;
  __cxa_exception *nextException;
  int handlerCount;

  static if (GNU_ARM_EABI_Unwinder)
  {
    __cxa_exception* nextPropagatingException;
    int propagationCount;
  }
  else
  {
    int handlerSwitchValue;
    const ubyte *actionRecord;
    const ubyte *languageSpecificData;
    _Unwind_Ptr catchTemp;
    void *adjustedPtr;
  }

  _Unwind_Exception unwindHeader;
}
*****************************

Ignoring the main guts of gdc's unwind implementation (should be very
similar in ldc and sdc by coincidence), let's instead focus on the exact
part where we try to match the exception object, you'd have something like
the following:
*****************************
if (ar_filter > 0)
  {
    // Positive filter values are handlers.
    void *catch_type = get_ttype_entry (&info, ar_filter);

    if (!foreign_exception)
      {
        if (_d_isbaseof (xh.object.classinfo, cast(ClassInfo)catch_type))
          saw_handler = true;
      }
    else
      {
        // == Case A ==
        // Null catch type is a catch-all handler; we can catch foreign
exceptions with this.
        if (catch_type is null)
          saw_handler = true;

        else if (ue_header.exception_class == __gxx_exception_class)
          {
            // == Case B ==
            // Typeinfo are directly compared, which might not be correct
if they aren't merged.
            void *except_typeinfo = ((cast(__cxa_exception *)(ue_header +
1)) - 1).exceptionType;

            if (catch_type == except_typeinfo)
              saw_handler = true;

            // == Case C ==
            // Typeid are compared between catch type and some specially
crafted C++ Exception class.
            if (cast(ClassInfo)catch_type is typeid(UnknownCPPException))
              saw_handler = true;
          }
      }
  }
*****************************

Now let's discuss the three cases:

- Case A:
A `null` catch type never happens in D code, because `catch { }` is always
re-written to `catch(Throwable) { }` (this might have changed since 2.066,
but I doubt it).  So any kind of `catch(...)` equivalent in D would make
this is a valid candidate for catching not just C++ exceptions, but also
exceptions in any language that use libunwind for EH (Ada, Go, Java,
etc...).  Bonus, we don't need to know anything about C++ exception objects
or typeinfo to achieve this.

In the other cases, checking the exception class is simple enough,
libunwind provides a field for that, and just requires to declare it
somewhere on the D bindings side.

- Case B:
Explicitly try to match a C++ exception object.  Copying the
__cxa_exception struct from unwind-cxx.h, takes advantage of ABI
compatibility - though we must take care that they are kept in sync (not
that it ever changes in an incompatible way).  Also from the C++ unwind
library, is one small trick to get the C++ typeinfo pointer.  Where this
falls short is that ``catch_type == except_typeinfo`` will always be false
unless we expose some way to generate C++ typeinfo in D.  *Maybe* this
could be achieved by generating an pointer to an `extern(C++)` symbol to
the typeinfo - just need to get mangling right.  But this is the ugliest
thing we can possibly do.

- Case C:
An alternative to the catch all handler in case A, but instead test whether
the catch we are inspecting matches some explicit object (derived from
`Throwable` to be compatible with the existing compiler constraints).
Again, this can be extended for other languages (UnknownAdaException,
UnknownGoException, UnknownJavaException, etc...) - however, unlike case A,
we need to copy the exception class signature of other languages into the D
bindings.

With the possible exception being in case B, there is no way to return the
foreign language object from EH to the catch handler.  Using case C and
storing the caught exception object to a variable `catch
(UnknownCPPException e)` will always result with the object being equal to
`null`.

*****************************

Coming back round full circle to the original example, I've implemented
case C but instead using `Throwable` to make `catch { }` succeed in
catching C++ exceptions.

Compilation and Result:
$ gdc dcode.d cxxcode.cc
$ ./a.out
Unknown exception caught in C++, rethrowing...
Unknown exception caught in D, exiting...

Even throwing in D, and catching in C++ works thanks to C++'s existing
support for foreign language exceptions and `catch(...)`.  So moving
`compute` into D, we have the following.
*****************************
extern(C++) int compute(int a, int b)
{
  if (b == 0) {
    writeln("Exception thrown in D...");
    throw new Exception("Division by 0 in D!");
  }

  return a / b;
}
*****************************

Compilation and Result:
$ gdc dcode.d cxxcode.cc
$ ./a.out
Exception thrown in D...
Unknown exception caught in C++, rethrowing...
Unknown exception caught in D, exiting...


Andrei - Hope this answers your question.

--
Regards
Iain
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.puremagic.com/pipermail/digitalmars-d/attachments/20151112/1d6287e9/attachment-0001.html>


More information about the Digitalmars-d mailing list