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 + -
显示快捷键?