SourceForge.net Logo

[Release b]
<t4m_bug_vt>
Mon Jul 12 18:03:27 WEST 2004

In the current release of tendra4minix (a), after fixing <t4m_bug_usage>,
the program vt.C makes TenDRA emit the definition of the virtual table for
class Alloc<Thread>, thus violating the ARM rule for defining virtual
tables (this is done in the module containing the definition of the first
non-implicit, non-inline, non-pure virtual function declared in the class).
This rule is not contained in the spec X3J16/96-0225 WG21/N1043 (as far as
I know), but the compiler is supposed to follow it. It is a nasty bug that
causes duplicate definitions of virtual tables.

The phenomenon is subtle, since it disappears if we drop a single function
in any of the template classes (constructor o destructor). It also disappears
when a declaration of another virtual function is placed before the declaration
of Alloc destructor,

export template <class T>
class Alloc {
public:
  Alloc() {}
  virtual T *get();
  virtual ~Alloc();
};

This bug does not show up with non-template classes.

The function compile_virtual [../output/compile.c] decides whether to declare
or define (externally or locally) virtual tables in the current compilation
unit. And it finds that the virtual destructor 'Alloc<Thread>::~Alloc()' is
defined, so it generates an externally declared virtual table for class
'Alloc<Thread>'.

The origin of the problem is in an out-of-order invocation of function
define_template [instance.c]. This function is used to mark a template instance
for being instantiated later on during the calls to clear_templates
[instance.c]; this is done by inserting the template instance in a list named
'pending_templates', and the template instance is marked with the 'dspec_defn'
flag without checking whether it's really defined. The second action is
not a bug. The function copy_template [instance.c], which is in charge of
instantiating the template body, should discover that a template is not
defined in the current translation unit and then clear the 'dspec_defn' flag.

There is an spurious call to define_template in function reuse_id
[identifier.c], after all templates have been emitted,

compile_pending
  ...
  compile_function, id = GCAlloc<Thread>::GCAlloc(), force = 1
    make_tagdef, id = GCAlloc<Thread>::GCAlloc()
      enc_func_defn, id = GCAlloc<Thread>::GCAlloc()
        except_postlude, id = GCAlloc<Thread>::GCAlloc()
          make_constr, fn = GCAlloc<Thread>::GCAlloc(), n = m = DEFAULT_DESTR
            init_empty_base, gr = GCAlloc<Thread>::Alloc<Thread>
              init_default, t = Alloc<Thread>
                call_constr, id = Alloc<Thread>::~Alloc()
                  apply_func_id, id = Alloc<Thread>::~Alloc()
                    reuse_id, id = Alloc<Thread>::~Alloc(), suppress = 0
                      define_template, id = Alloc<Thread>::~Alloc(), expl = 0
                      define_template: returning
                    reuse_id: returning
                  apply_func_id: returning
                call_constr: returning
              init_default: returning
            init_empty_base: returning
          make_constr: returning
        except_postlude: returning
      enc_func_defn: returning
    make_tagdef: returning
  compile_function: returning
  ...
compile_pending: returning

Function compile_pending [compile.c] is invoked from process_files [main.c],
after the last call to clear_templates [instance.c] (that is, templ = 2) is
made, and just before the emission of the TDF capsule.

Possible solution: add a flag 'all_templates_cleared' to instance.c indicating
that clear_templates(2) has already been called; define_template calls
'copy_template (id, 1);' if all_templates_cleared is not zero. The flag, which
can be local to instance.c, gets activated just before returning from
clear_templates when templ = 2 (end of compilation unit),

/* this flag is for avoiding marking with dspec_defn template instances that
   are not really defined, during late calls to function define_template */

static int all_templates_cleared = 0 ;

At the end of function clear_templates [instance.c],

    if ( templ == 2 ) {
	all_templates_cleared = 1 ;
    }
    return ;

In function define_template [instance.c], call immediately copy_template for
having either the instance generated or the dspec_defn flag cleared again,

		/* Template functions */
		if ( !( ds & dspec_defn ) ) {
		    CONS_id ( id, pending_templates, pending_templates ) ;
		    COPY_dspec ( id_storage ( id ), ( ds | dspec_defn ) ) ;
		    COPY_loc ( id_loc ( id ), crt_loc ) ;
		    if ( all_templates_cleared ) {
			copy_template ( id, 1 ) ;
		    }
		}

Observe that calling copy_template here is supposed to be all right, since
function define_template already has a call to copy_templates a few lines
below.

PS. This solution has not been adopted because of the appearance of a related
bug (<t4m_bug_vt_dtor>). Instead, I have used a (possibly better) solution to
both bugs. See <t4m_bug_vt_dtor>.


Notes.

[ ] DECL_SPEC of Alloc<Thread>::~Alloc before spurious definition takes place,

    33554432 = 0x2000000 = dspec_main
    16777216 = 0x1000000 = dspec_instance
     4194304 = 0x0400000 = dspec_ignore
     2097152 = 0x0200000 = dspec_cpp
       65536 = 0x0010000 = dspec_public
       32768 = 0x0008000 = dspec_typedef
        4096 = 0x0001000 = dspec_virtual
         128 = 0x0000080 = dspec_extern
    --------------------------------------
    56725632 = 0x3619080