diff --git a/mak/SRCS b/mak/SRCS index a4df4d7822..0a915726c9 100644 --- a/mak/SRCS +++ b/mak/SRCS @@ -302,6 +302,7 @@ SRCS=\ src\rt\deh_win64_posix.d \ src\rt\dmain2.d \ src\rt\dwarfeh.d \ + src\rt\ehalloc.d \ src\rt\invariant.d \ src\rt\lifetime.d \ src\rt\llmath.d \ diff --git a/src/core/thread.d b/src/core/thread.d index cbf719b05a..f69defca35 100644 --- a/src/core/thread.d +++ b/src/core/thread.d @@ -223,6 +223,8 @@ version( Windows ) void append( Throwable t ) { + if (t.refcount()) + ++t.refcount(); if( obj.m_unhandled is null ) obj.m_unhandled = t; else @@ -377,6 +379,8 @@ else version( Posix ) void append( Throwable t ) { + if (t.refcount()) + ++t.refcount(); if( obj.m_unhandled is null ) obj.m_unhandled = t; else diff --git a/src/object.d b/src/object.d index 096d6e4cc0..f702367d21 100644 --- a/src/object.d +++ b/src/object.d @@ -16,6 +16,8 @@ private extern (C) void rt_finalize(void *data, bool det=true); } +public @trusted @nogc nothrow pure extern (C) void _d_delThrowable(scope Throwable); + // NOTE: For some reason, this declaration method doesn't work // in this particular file (and this file only). It must // be a DMD thing. @@ -1704,6 +1706,18 @@ class Throwable : Object */ Throwable next; + private uint _refcount; // 0 : allocated by GC + // 1 : allocated by _d_newThrowable() + // 2.. : reference count + 1 + + /** + * Returns: + * mutable reference to the reference count, which is + * 0 - allocated by the GC, 1 - allocated by _d_newThrowable(), + * and >=2 which is the reference count + 1 + */ + @system @nogc final pure nothrow ref uint refcount() return scope { return _refcount; } + @nogc @safe pure nothrow this(string msg, Throwable next = null) { this.msg = msg; @@ -1719,6 +1733,12 @@ class Throwable : Object //this.info = _d_traceContext(); } + @trusted nothrow ~this() + { + if (next && next._refcount) + _d_delThrowable(next); + } + /** * Overrides $(D Object.toString) and returns the error message. * Internally this forwards to the $(D toString) overload that diff --git a/src/rt/deh_win32.d b/src/rt/deh_win32.d index 6d44847550..ad5e030f73 100644 --- a/src/rt/deh_win32.d +++ b/src/rt/deh_win32.d @@ -611,21 +611,33 @@ int _d_exception_filter(EXCEPTION_POINTERS *eptrs, } /*********************************** - * Throw a D object. + * Throw a D instance of Throwable. */ -private void throwImpl(Object h) +private void throwImpl(Throwable h) { - // @@@ TODO @@@ Signature should change: h will always be a Throwable. //printf("_d_throw(h = %p, &h = %p)\n", h, &h); //printf("\tvptr = %p\n", *(void **)h); + + /* Increment reference count if `h` is a refcounted Throwable + */ + auto refcount = h.refcount(); + if (refcount) // non-zero means it's refcounted + h.refcount() = refcount + 1; + _d_createTrace(h, null); RaiseException(STATUS_DIGITAL_MARS_D_EXCEPTION, EXCEPTION_NONCONTINUABLE, 1, cast(void *)&h); } -extern(C) void _d_throwc(Object h) +/*************************************** + * The compiler converts: + * throw h; + * into a call to: + * _d_throwc(h); + */ +extern(C) void _d_throwc(Throwable h) { // set up a stack frame for trace unwinding version (AsmX86) diff --git a/src/rt/deh_win64_posix.d b/src/rt/deh_win64_posix.d index 5b17b23137..4338392b91 100644 --- a/src/rt/deh_win64_posix.d +++ b/src/rt/deh_win64_posix.d @@ -219,7 +219,7 @@ size_t __eh_find_caller(size_t regbp, size_t *pretaddr) * Throw a D object. */ -extern (C) void _d_throwc(Object h) +extern (C) void _d_throwc(Throwable h) { size_t regebp; @@ -242,6 +242,12 @@ extern (C) void _d_throwc(Object h) else static assert(0); + /* Increment reference count if `h` is a refcounted Throwable + */ + auto refcount = h.refcount(); + if (refcount) // non-zero means it's refcounted + h.refcount() = refcount + 1; + _d_createTrace(h, null); //static uint abc; diff --git a/src/rt/dwarfeh.d b/src/rt/dwarfeh.d index fb00bb6694..0f61898a94 100644 --- a/src/rt/dwarfeh.d +++ b/src/rt/dwarfeh.d @@ -183,6 +183,12 @@ extern(C) void _d_throwdwarf(Throwable o) eh.push(); // add to thrown exception stack //printf("_d_throwdwarf: eh = %p, eh.next = %p\n", eh, eh.next); + /* Increment reference count if `o` is a refcounted Throwable + */ + auto refcount = o.refcount(); + if (refcount) // non-zero means it's refcounted + o.refcount() = refcount + 1; + /* Called by unwinder when exception object needs destruction by other than our code. */ extern (C) static void exception_cleanup(_Unwind_Reason_Code reason, _Unwind_Exception* eo) diff --git a/src/rt/ehalloc.d b/src/rt/ehalloc.d new file mode 100644 index 0000000000..132582608c --- /dev/null +++ b/src/rt/ehalloc.d @@ -0,0 +1,125 @@ +/** + * Exception allocation, cloning, and release compiler support routines. + * + * Copyright: Copyright (c) 2017 by D Language Foundation + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Walter Bright + * Source: $(DRUNTIMESRC src/rt/_dwarfeh.d) + */ + +module rt.ehalloc; + +//debug = PRINTF; + +debug(PRINTF) +{ + import core.stdc.stdio; +} + +/********************************************** + * Allocate an exception of type `ci` from the exception pool. + * It has the same interface as `rt.lifetime._d_newclass()`. + * The class type must be Throwable or derived from it, + * and cannot be a COM or C++ class. The compiler must enforce + * this. + * Returns: + * default initialized instance of the type + */ + +extern (C) Throwable _d_newThrowable(const TypeInfo_Class ci) +{ + debug(PRINTF) printf("_d_newThrowable(ci = %p, %s)\n", ci, cast(char *)ci.name); + + assert(!(ci.m_flags & TypeInfo_Class.ClassFlags.isCOMclass)); + assert(!(ci.m_flags & TypeInfo_Class.ClassFlags.isCPPclass)); + + import core.stdc.stdlib : malloc; + void* p = malloc(ci.initializer.length); + if (!p) + { + import core.exception : onOutOfMemoryError; + onOutOfMemoryError(); + } + + debug(PRINTF) printf(" p = %p\n", p); + + // initialize it + p[0 .. ci.initializer.length] = ci.initializer[]; + + if (!(ci.m_flags & TypeInfo_Class.ClassFlags.noPointers)) + { + // Inform the GC about the pointers in the object instance + import core.memory : GC; + + GC.addRange(p, ci.initializer.length, ci); + } + + debug(PRINTF) printf("initialization done\n"); + Throwable t = cast(Throwable)p; + t.refcount() = 1; + return t; +} + + +/******************************************** + * Delete exception instance `t` from the exception pool. + * Must have been allocated with `_d_newThrowable()`. + * This is meant to be called at the close of a catch block. + * It's nothrow because otherwise any function with a catch block could + * not be nothrow. + * Input: + * t = Throwable + */ + +nothrow extern (C) void _d_delThrowable(Throwable t) +{ + if (t) + { + debug(PRINTF) printf("_d_delThrowable(%p)\n", t); + + /* If allocated by the GC, don't free it. + * Let the GC handle it. + * Supporting this is necessary while transitioning + * to this new scheme for allocating exceptions. + */ + auto refcount = t.refcount(); + if (refcount == 0) + return; // it was allocated by the GC + + if (refcount == 1) + assert(0); // no zombie objects + + t.refcount() = --refcount; + if (refcount > 1) + return; + + TypeInfo_Class **pc = cast(TypeInfo_Class **)t; + if (*pc) + { + TypeInfo_Class ci = **pc; + + if (!(ci.m_flags & TypeInfo_Class.ClassFlags.noPointers)) + { + // Inform the GC about the pointers in the object instance + import core.memory : GC; + GC.removeRange(cast(void*) t); + } + } + + try + { + import rt.lifetime : rt_finalize; + rt_finalize(cast(void*) t); + } + catch (Throwable t) + { + assert(0); // should never happen since Throwable.~this() is nothrow + } + import core.stdc.stdlib : free; + debug(PRINTF) printf("free(%p)\n", t); + free(cast(void*) t); + } +} +