Ctor/Dtor Design
================

This file documents the use the constructors (CTOR) and destructors (DTOR).
It includes the design for error-handling.


History
-------

91/11/21 - JWW - initial design


Common Stuff
============

    typedef void CTOR_DTOR();       // ctor/dtor function

    struct THREAD_CTL               // controls each execution thread
    {   CTOR_BLOCK_LIST *autos;     // - active CTOR blocks for autos
        TRY_BLOCK_REF *tries;       // - active try blocks
        char exception[1];          // - exception (variable-sized)
    }

Run-time Globals
================

    THREAD_CTL thread;              // current execution thread


Automatic Objects
=================

- CTOR called for object as the definition is executed
- corresponding DTOR must be called when block is exited (or popped by error
  handling)

- need structure to indicate which items have been CTOR'd (view block
  as C++ object which needs to CTOR'd on entry, DTOR'd on exit, and is
  modified as each automatic object in the scope has been CTOR'd)

    struct CTOR_BLOCK_LIST          // list of items CTOR'd (on stack)
        CTOR_BLOCK_LIST *prev;      // - previous block
        BLOCK_DTORS *dtors;         // - destructors list
        CTOR_BLOCK_ITEM *list[1];   // - list of elements CTOR'd
    };

    struct BLOCK_DTORS              // items to be dtor'd (static r/o)
    {   unsigned count;             // - # objects
        CTOR_DTOR *list[1];         // - dtor's
    };

    - the two lists must correspond

- implementation:

    - CTOR_BLOCK_LIST allocated as an automatic object (on stack) for each
      block (if req'd)

    - CTOR'd on block entry by:
        ctor_block_list( CTOR_BLOCK_LIST *list
                       , BLOCK_DTORS *dtors
                       , unsigned count );
        - all item pointers are set to NULL to indicate not CTOR'd
        - links to previous block for error handling

    - DTOR'd on block exit by:
        dtor_block_list( CTOR_BLOCK_LIST *list );
        dtor_block_list_curr();

    - after a successful CTOR of an item:
         ctor_block_item( CTOR_BLOCK_LIST *list
                        , void *item
                        , unsigned index );
         ctor_block_item_curr( void *item
                             , unsigned index );

- method:

    - goto out of block
        - generates "IC_BLOCK_EXIT scope" for each scope exited

    - execution of definition:
        - generates "IC_BLOCK_CTOR sym" after each class variable CTOR'D

    - execution of IC_BLOCK_OPEN:
        - inspects scope to see if CTOR_BLOCK_LIST req'd
        - allocates automatic variable if req'd

    - execution of IC_BLOCK_EXIT
        - inspects scope to see if CTOR_BLOCK_LIST req'd
        - generates call to dtor_block_item_curr, if req'd

    - execution of IC_BLOCK_CLOSE
        - inspects scope to see if CTOR_BLOCK_LIST req'd
        - generates call to dtor_block_item_curr, if req'd

    - execution of IC_BLOCK_CTOR
        - generates call to ctor_block_item_curr

    - note: perhaps we should set flag somewhere to indicate that a class
            element CTOR has occurred in a scope (this could be done when
            IC_BLOCK_CTOR is generated by the front-end)


Error Handling
==============

- type of throw expression determines which of the active catch-blocks
  receives control

- there is a need to uniquely identify (CATCH_NAME) each possible type
  that can be caught

    typedef char *CATCH_NAME;

- implementation:

    - in a COMDEF generate a unique 1-byte global variable whose name
      corresponds to the type (note: T, const T, T&, const T& cause
      matches )
        - two names are generated for bare-name references
        - if we never use the storage in the COMDEF, the linker could be
          upgraded to support "link-time enumeration"
        - we could use the storage to speed the search for a catch


- need structure to record an active try-block:

    struct TRY_BLOCK_REF            // reference (on stack) to try block
    {   TRY_BLOCK_REF *prev;        // - previous active block
        TRY_BLOCK *try_block;       // - reference to block
    }

    struct TRY_BLOCK                // an active try (static r/o)
    {   unsigned count;             // - # catch blocks
        CATCH_ITEM list[1];         // - list of catch blocks
    }

    struct CATCH_ITEM               // an active catch block for a try
        void (*rtn)();              // - start of code
        CATCH_NAME catch_name;      // - unique name
    }


- need structure to record possible throw expressions which can be caught:

    struct THROW_LIST               // list of expression types (static r/o)
    {   unsigned count;             // - # possible expressions
        THROW_EXPR list[1];         // - possible expressions
    }

    struct THROW_EXPR               // define possible expression (static r/o)
    {   void (*copy)();             // - copy routine
        CATCH_NAME catch_name;      // - unique name
    };

- implementation:

    - view try block as C++ object

    - block entry:
        ctor_try_block( TRY_BLOCK_REF *ref, TRY_BLOCK *try_block );

    - block exit:
        dtor_try_block( TRY_BLOCK_REF *ref );
        dtor_try_block_curr();

- throwing an exception

    - how does a DTOR get called for the expression ?

    - if no expression:
        - if error-handling active, call rethrow_exception();
            - does this use the converted expression ?
            - if no, a new THROW_LIST is required
        - else call unexpected()
    - else causes call:
        - if error-handling active
            - do we free old exception
        - call throw_exception( THROW_LIST *list, expression );

    - search back through TRY_BLOCK_REF blocks to locate CATCH_BLOCK and the
      TRY_BLOCK_REF for the item.

    - if none: call unexpected();

    - unwind stack (described later) and jump to catch block

- catch block processing: same as any other block


Compilation
===========

try {                       ctor_try_block( &try_block );
                            ... stuff for { ... }
}                           goto lab_4;

                        lab_1:
catch( object &o ) {        object internal;
                            object &o = internal;
                            ctor_catch( &internal );
                            [ ctor_block_item_curr( &o, index ); ] if req'd
                            ... stuff for { ... }
                            [ dtor_block_item_curr(); ] if req'd
}                           goto lab_4;

                        lab_2:
catch( object o ) {         object o;
                            ctor_catch( &o );
                            [ ctor_block_item_curr( &o, index ); ] if req'd
                            ... stuff for { ... }
                            [ dtor_block_item_curr(); ] if req'd
}                           goto lab_4;

                        lab_3:
catch( ... ) {              ... stuff for { ... }
                            goto lab_4;
}
                        lab_4;

- the copy constructor for the indicated object is invoked by the run-time
  routine "ctor_catch"


Stack Unwinding:
================

- the active CTOR_BLOCK_LIST items must be processed until one preceding
  the active try block is encountered

- for each such CTOR_BLOCK_LIST, call the appropriate destructor for the
  CTOR'd items


Compiling CTOR's
=================

- housekeeping may be required:
    - have to set up pointers to virtual base tables
    - have to set up pointers to virtual function tables

- a constructor may only partially initialize an object before an exception
  is thrown

- the initialized parts must be destructed (in reverse order)

- we need structures to record what has been initialized:
    - use CTOR_BLOCK_LIST, BLOCK_DTORS to record this

- this can be accomplished by using a second CTOR_BLOCK_LIST within the
  outermost block of a constructor; each constructor call is followed by an
  appropriate "ctor_block_item_curr" call

- no modifications are required for stack unwinding

- note: the same BLOCK_DTORS is used for all ctor's/dtor's for a class (there
        need be only one per class)

- implementation: ctor (can be many)

    - to start, generate everthing in-line (what follows is optimized version)

    - in declaration order, generate initialization for base classes, then
      for class members, then generate function-body code

        - if mem-initializers exist, (base classes, class members)
            - fill in VFPTR pointers if required
            - generate in-line initialization code based on specifications
          else
            - if more than one ctor exists
                - fill in VBPTR, VFPTR pointers if required
                - call generated-default-ctor
              else
                - generate default ctor in-line

        - all virtual base classes (pg.92) are constructed before any
          non-virtual base classes
            - second parameter (0,1) passed to such constructors; when 1, the
              exact type of the class is known
                - virtual bases are constructed
                - construct all virtuals
            - subsequent calls to constructors pass 0 to ensure that the
              virtuals are not re-initialized

    - generated-default-ctor: internally-named routine which is equivalent to
      C::C() when this constructor does not exist (so, it must have a
      different name than C::C() that is specified.

        - chain this after the [optional] one for the outermost block

        - just before block is exited, unchain this block (we don't want it
          to cause dtor'ing when the block is successfully exited)

        - signal this with an "IC_CTOR_OK scope" instruction

    - common code can be factored out of all CTOR's for a class into a
      routine "generate_default_ctor_code"

    - for a class C, if no C::C() is defined, call the generated-default-ctor

    - use thunks to transfer control to constructors, if necessary

Compiling DTOR's
=================

- implementation: dtor (can only be one)

    - in opposite order to ctor: generate function-body code, destruct class
      members, destruct base classes

    - a CTOR_BLOCK_LIST can be created for the object being destructed; this
      should be linked just before the optional CTOR_BLOCK_LIST for the
      outermost block

    - this will ensure that any exceptions thrown during DTOR'ing will still
      cause the components to be destructed

    - when the outmost block is exited, call dtor_block_list an extra time
      for the extra block
        - determined by IC_BLOCK_EXIT, IC_BLOCK_CLOSE


Optimization Considerations
===========================

- when there is no optimization:
    - if used, a dtor and a generated-default-ctor will be generated as
      static functions within the module
    - if used, a BLOCK_DTORS for a class will be generated as COMDEF r/o data

- pragmas can be introduced to:
    - reference above functions as externals defined elsewhere
    - define above functions as public
    - reference externally the BLOCK_DTORS for a class
    - define BLOCK_DTORS as public COMDEF r/o data



Example
=======

struct A        struct B        struct C : public A, public B   struct D
{ int A1;       { int b1;       { C();                          { int d1;
  A();            A b2;           ~C();                           D( int n )
  ~A();         }               }                                 { d1 = n };
}                                                               }

C::C
{   D d(4);
    throw d;
}

void main()
{   try{
        B vb;
        C vc;   // note: C::C throws error
        exit(0);
    }
    catch( D d ) {
        exit(10);
    }
}


Pseudo-Code compiled:
---------------------

- assume that compiler thinks that exit can return
    - if it knows that it will not return, then that is a block exit and
      dead code follows the call (dtor_block_list_curr moved before exit
      and no goto generated)
- assume that compiler knows that throw will not return

public
C::C()  { struct CTOR_BLOCK_LIST v0;    // for ctor (2 items)
          struct D d;
          ctor_block_list( &v0, &block_dtor_C, 2 );
          generated_default_ctor_code_C( this, &v0 );
          d.D( 4 );
          ctor_block_item_curr( &d, 0 );
          thread.global_exception.D::D&( this );
          dtor_block_list_curr();
          throw( &stat_2 );
        }


public
main()  { TRY_BLOCK_REF v0;
          ctor_try_block( &v0, &stat_0 );
          { struct CTOR_BLOCK_LIST v1;  // with two elements
            struct B vb;
            struct C vc;
            ctor_block_list( &v1, &stat_1, 2 );
            generated_default_ctor_B( &vb );
            ctor_block_item_curr( &vb, 0 );
            C::C( &vc );
            ctor_block_item_curr( &vc, 1 );
            exit(0);
            dtor_block_list_curr();
            goto lab_2;
          }
lab_1:    { struct D d;
            catch_ctor( &d );
            exit(10);
            dtor_block_list_curr();
          }
lab_2:
          dtor_try_block_curr();
        }

static generated_default_ctor_code_B( B *bp, CTOR_BLOCK_LIST *v2 )
        {
            ctor_block_list( v2, &block_dtor_B, 1 );
            A( &bp->a );
            ctor_block_item_curr( &bp->a, 0 );
        }

static generated_default_ctor_B( B *bp )
        {   struct CTOR_BLOCK_LIST v2;  // with one element
            generated_default_ctor_code_B( bp, &v2 );
            block_list_pop();
        }

static generated_default_ctor_code_C( C *c, CTOR_BLOCK_LIST *bc )
        {
            ctor_block_list( c, bc, 2 );
            A( (A*)c );
            ctor_block_item_curr( (A*)c, 0 );
            generated_default_ctor_B( (B*)c );
            ctor_block_item_curr( (B*)c, 1 );
        }

Static Read-only Data
---------------------

static TRY_BLOCK stat_0 =           // with one element
    {   1
    ,   { &lab_1, catch_name_D }
    };

static BLOCK_DTORS stat_1 =         // with two elements
    {   2
    ,   &generated_default_ctor
    ,   &C::~C
    };

static THROW_LIST stat_2 =          // with 1 element
    {   1
    ,   { NULL, catch_name_D }
    };


COMDEF Data
-----------

BLOCK_DTORS block_dtor_B =          // with one element
    {   1
    ,   &A::~A
    };

struct THREAD thread;               // with size of largest exception thrown


Stack when Throw executed:
-------------------------

- return address (C::C)
----------------------------- C::C
- return address (main)
----------------------------- main
- auto: vc
- auto: vb
- auto: v1 = { 2, NULL, &stat_1, { &vb, &vc } }
- auto: v0 = { 1, NULL, &stat_0 }
- return address (system )
----------------------------- system


System Data:
-----------

Thread = { &main.v1         // - autos
         , &main.v0         // - tries
         , { 4 }            // - D for exception
         };

Compiling CTOR Header
=====================

- ctor for class without any virtual bases:
    ctor( [ this, ] visible parameters )
- ctor for class with virtual bases:
    ctor( [ this, flag, ] visible parameters )
    - flag & 1 ==> object being ctor'd has exact type of object

- compiled ctor consists of:
    - ctor header
    - function body (supplied)

- default constructor consists only of
    - ctor header

- ctor header is compiled as:

    - if class has virtual bases:
        - generate: " if( 0 == ( flag & 1 ) ) goto label_0 "
        - generate code to initialize VBPTR's

    - if VFPTR's required for class (addressing in this section uses VBPTR's)
        - if class has virtual bases
            - generate: " label_0: "
        - generate code to fill in VFPTR's
        - fill in construction displacements in virtual base classes

    - if class has virtual bases: (addressing in this section is direct)
        - if VFPTR's were required
            - generate: " if( 0 == ( flag & 1 ) ) goto label_1 "
        - for all virtual base classes (in forward order)
            - if mem-initializer
                - generate mem-initializer-code( adjusted(this), 0, ... )
              else
                - call ctor( adjusted(this), 0 );
        - if VFPTR's were required
            - generate: " label_1: "
          else
            - generate: " label_0: "

    - for all non-virtual bases (in forward order)
        - if mem-initializer
            - generate mem-initializer-code( &this->base, [0], ... )
          else
             - call ctor( &this->base, [0] );

    - for all class data members (in forward order)
        - if mem-initializer
            - generate mem-initializer-code( &this->member, [1] ... )
          else
             - call ctor( &this->base, [1] );

    - if class has virtual bases: (addressing in this section is direct)
        - set construction-displacement to 0 in virtual base classes

Compiling DTOR Header
=====================

- dtor for class without any virtual bases:
    dtor( [ this, ] );
- dtor for class with virtual bases:
    dtor( [ this, flag, ] )
    - flag & 1 ==> object being ctor'd has exact type of object

- compiled dtor consists of:
    - function body (supplied)
    - dtor header

- default constructor consists only of
    - dtor header

- dtor header is compiled as:

    - if VFPTR's required for class (addressing in this section uses VBPTR's)
        - generate code to fill in VFPTR's
        - fill in construction displacements in virtual base classes

    - for all class data members (in backward order)
        - call dtor( &this->base, [1] );

    - for all non-virtual bases (in backward order)
        - call dtor( &this->base, [0] );

    - if class has virtual bases: (addressing in this section is direct)
        - generate: " if( 0 == ( flag & 1 ) ) goto label_2 "
        - for all virtual base classes (in backward order)
            - call dtor( adjusted(this), 0 );
        - generate: " label_2: "
        - set construction-displacement to 0 in virtual base classes
