dtor93.txt

来自「开放源码的编译器open watcom 1.6.0版的源代码」· 文本 代码 · 共 692 行 · 第 1/2 页

TXT
692
字号
Design of Destruction/Exception Implementation for Release 10.0
===============================================================

(1) History:
    -------

    - 93/08/19 (JWW) first document


(2) Mission:
    -------

    - devise a methodology to support destruction of C++ objects

    - should involve minimal overhead when exceptions are disabled
      and appropriate compiler options are specified


(3) Overall Design Decisions
    ------------------------

    - support direct call of destructors for speed

    - no requirement of tables when exceptions disabled and direct DTOR
      calls are used

    - be able to mix direct-call and table-driven approaches

    - when exceptions are enabled, a routine must be compiled using the
      table-driven approach; additionally, the direct-call mechanism may
      be used to increase speed with a code-space penalty

    - when exceptions are disabled, a routine may be compiled using either
      method

    - use a "state-variable" approach for the table-driven version, as
      opposed to an instruction-pointer approach

    - isolate construction and usage of data structure to permit migration
      to possible operating-system dependent methods if they become available

    - should permit the degeneration of constructors and destructors into
      component, actual, and deletion parts


(4) Background
    ----------

    - current method needs upgrading since ANSI committee has just mandated
      the requirements for handling temporary objects (on a per-statement
      basis, opposed to a per-block basis in our current implementation)

    - current method is entirely table-driven (on a block basis) and can be
      improved by:
      - directly calling DTOR's (instead of interpreting a table) at end
        of blocks (this will increase code space and decrease data space
        when exceptions are disabled)
      - registering tables on a routine basis (instead of a block basis)

    - the ANSI standard requires exception handling for standard libraries
      and so the enabling of exception handling will become the normal
      mode in which programs execute, instead of the one used only when
      exception capabilities are directly coded in the program.

    - a "state-variable" approach to table handling is used because:
      - it is portable
      - code generation must be restricted when instruction pointer is used,
        since the run-time system would be required to access return addresses
        on the stack for routines which have called other routines

    - table-driven implementation is required for exceptions and could be
      used when exceptions are disabled; this will be allowed since it is
      anticipated to use less space than direct calls of DTOR's

    - implementation of direct calls of DTOR's will be faster and so can be
      used by itself when exceptions are disabled or additionally when
      exceptions are enabled.


(5) Direct-call of Destructors
------------------------------

    - poses no new problems

    - at points where there are (or would be) calls to run-time for
      destruction based upon the tables, calls are generated directly


(6) Data Structure for Tables
-----------------------------

    - as before, a combination of R/W (read-write) and R/O (read-only) control
      blocks

    - the R/W blocks are linked together by a registration run-time call at
      the start of a function; R/W block points at R/O block; deregistration
      routine is called at end of routine to unlink the R/W block

    - R/W block contains state-variable (SV) representing the state with 0
      reserved to indicate that nothing has happened yet

    - R/W block may contain a number of flags indicating which optional
      sequences of items need to be destructed

    - R/O block contains a constant header, a section of DTOR items, and a
      section of commands; this is an optimization of a design involving
      only commands (discussed first)

    - one view of the R/O table is that it is a sequence of commands which
      are executed to control destruction and are searched during throws;
      the commands are:
      - destruct object
      - end of destruction sequence
      - start of try block
      - end of catch block
      - function exception specification
      - reset the state variable

    - consider the following function:

        void foo() throw( int, float )
        {
            S v1;
            S v2;
            try {
                S v3;
                throw v3;
            } catch( S v4 ) {
                S v5;
            } catch( int ) {
                S v6;
            }
            expr ? tmp( t1 ) : tmp( t2 );
        }

        where v1 .. v6 and t1 .. t2 all require destruction.

    - conceptually, the R/O table could be represented as follows:

        1   < FNEXC, control block for function-exc. spec. >
        2   < DTOR, addr[dtor], offset(v1) >
        3   < DTOR, addr[dtor], offset(v2) >
        4   < TRY, &try_cb (control block for try) >
        5   < SET_SV, 2 >
        6   < DTOR, addr[dtor], offset(v3) >
        7   < SET_SV, 2 >
        8   < DTOR, addr[dtor], offset(v4) >
        9   < CATCH, 3 >
        10  < DTOR, addr[dtor], offset(v5) >
        11  < SET_SV, 2 >
        12  < CATCH, 3 >
        13  < DTOR, addr[dtor], offset(v6) >
        14  < SET_SV, 2 >
        15  < DTOR, addr[dtor], offset(t1) >
        16  < SET_SV, 2 >
        17  < DTOR, addr[dtor], offset(t2) >
        18  < TEST_FLAG, 0, 13, 15 >

      where the R/W block is:

        addr[ previous R/W block ]
        addr[ R/O block ]
        state-variable (sv)
        bit flags[1];

      and the routine is pseudo-compiled as:

1 ==> generated for table-driven method
2 ==> generated for direct-call method

/////   void foo() throw( int, float )
/////   {
1       Register( RW, RO )      // link stuff, sv <- 0
/////       S v1;
        ctor( v1 )
1       RW.sv <- 2
/////       S v2;
        ctor( v2 )
1     [ RW.sv <- 3 ]            // optimized away
/////       try {
1       RW.sv <- 4
        select( setjmp for try )
        ( labels: C0, C1, C2, C3 )
    C0  label
/////           S v3;
        ctor( v3 )
1       RW.sv <- 6
/////           throw v3;
        throw v3
/////       }    
1     [ destruct( 2 ) ]         // dead-coded away
2,1   [ RW.sv <- 2    ]
2     [ dtor( v3 )    ]
      [ goto L_1      ]
/////         catch( S v4 ) {
    C1  label
/////           S v5;
        ctor( v5 );
1       RW.sv <- 10
/////       }
1       destruct( 2 )
2,1     RW.sv <- 2
2       dtor( v5 )
        catch_over();
        goto L_1   
/////         catch( int ) {
    C2  label
/////           S v6;
        ctor( v6 )
1       RW.sv <- 13
/////       }
1       destruct( 2 )
2&1     RW.sv <- 2
2       dtor( v6 )
        catch_over();
      [ goto L_1 ]
    C_3 label
    L_1 label
/////       expr ?
        branch-true L_2
/////              tmp( t1 )
        ctor( t1 )
1       RW.flag[0] <- 1
1       RW.sv <- 15
/////                        :
1       destruct( 2 )
2&1     RW.sv <- 2
2       dtor( t1 )
        goto L_3
    L_2 label
/////                          tmp( t2 )
        ctor( t2 )
1       RW.flag[0] <- 0
1       RW.sv <- 17
/////                                   ;
1       destruct( 2 )
2&1     RW.sv <- 2
2       dtor( t2 )
/////   }
    L_3 label
1       destruct( 0 )
2&1     RW.sv <- 1
2       dtor( v2 )
2&1     RW.sv <- 0 
2       dtor( v1 )
1       deregister RW
        return

    - claim: the data structure provides enough information to implement
      destruction ( destruct n ==> destruct from current point up to
      the place where the state variable is n) and to implement catching
      exceptions

    - because the original table cannot indexed efficiently and
      because it is anticipated that most of the memory in R/O tables
      will be destructor (DTOR) commands, the table will be implemented
      as two tables where the first table consists of <addr,addr> pairs
      interpreted as follows:

      - when the first item is non-zero, it is a destructor-function address
        and the second operand is the offset/address of the item to be
        destructed

      - when the first item is zero, it is the address of a command

    - the revised table appears as follows:
    
        1   < 0, &C1 >
        2   < addr[dtor], offset(v1) >
        3   < addr[dtor], offset(v2) >
        4   < 0, &C2 >
        5   < 0, &C3 >
        6   < addr[dtor], offset(v3) >
        7   < 0, &C3 >
        8   < addr[dtor], offset(v4) >
        9   < 0, &C4 >
        10  < addr[dtor], offset(v5) >
        11  < 0, &C3 >
        12  < 0, &C4 >
        13  < addr[dtor], offset(v6) >
        14  < 0, &C3 >
        15  < addr[dtor], offset(t1) >
        16  < 0, &C3 >
        17  <  addr[dtor], offset(t2) >
        18  < 0, &C5 >

        - the following commands may reside anywhere in memory

        C1: < FNEXC, control block for function-exc. spec. >
        C2: < TRY, control block for try >
        C3: < SET_SV, 2 >
        C4: < CATCH, 3 >
        C5: < TEST_FLAG, 0, 13, 15 >

        - C3, C4, and C5 can be comdat since they are likely to be repeated
          for other control blocks

        - if addresses can be reserved in the command-ptr field, then
          special addresses can be used to represent common commands; if
          the addresses 0-15 are reserved:

          - 0:13  ==> SET_SV n ( n = 0, 1, ... 7 )
          - 14    ==> VIRTUAL_BASE
          - 15    ==> DIRECT_BASE

        - the catch command can consist only of a code and can be compiled
          directly in front of the try command

        - the try and function-exception commands contain the actual control
          information (they don't point at control blocks)


    - code when only table-driven method is used

/////   void foo() throw( int, float )
/////   {
1       Register( RW, RO )      // link stuff, sv <- 0
/////       S v1;
        ctor( v1 )
1       RW.sv <- 2
/////       S v2;
        ctor( v2 )
1       RW.sv <- 3
/////       try {
1       RW.sv <- 4
        select( setjmp for try )
        ( labels: C0, C1, C2, C3 )
    C0  label
/////           S v3;
        ctor( v3 )
1       RW.sv <- 6
/////           throw v3;
        throw v3
/////       }    
1     [ destruct( 2 ) ]       // dead-coded away
      [ goto L_1      ]
/////         catch( S v4 ) {
    C1  label
/////           S v5;
        ctor( v5 );
1       RW.sv <- 10
/////       }
1       destruct( 2 )
        catch_over();
        goto L_1   
/////         catch( int ) {

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?