SourceForge.net Logo

[Release b]
<t4m_bug_copy_func_args>
Thu May  6 20:33:28 WEST 2004

In the current release of tendra4minix (a), after fixing <t4m_bug_tok_recur>,
the compilation of the file tok_recur.C produces this error

Internal error: 'tag 5' used but not defined.

This error is generated by the frontend [src/producers/common/construct/],
inside the function write_capsule_body [../output/capsule.c]. The tag mentioned
is found in the data structure 'static VAR_INFO vars[VAR_total]', declared in
the same file. It is an array of 6 copies of the structure VAR_INFO, and the
"tags" are stored in the entry 0 of this array, 'vars[0]'. Tags uses are stored
in 'unsigned char *vars[0].uses' and their definitions are stored in
'string *vars[0].names'. What is happening is that 'vars[0].names[5]' is the
null pointer (that is, there is no definition), but 'vars[0].uses[5]' says that
the tag 5 has been used in the file, and thus the above error is generated.

Since the structure 'vars' is static, it is accessed only from the same module
capsule.c, and so it is not difficult to locate the places where it is written
and, in particular, where the entries 'vars[0].names[5]' and 'vars[0].uses[5]'
are written. To find them, we instrument the functions capsule_no, capsule_name,
capsule_id, record_usage, and clear_usage, in the following way

ulong capsule_no
    PROTO_N ( ( s, v ) )
    PROTO_T ( string s X int v )
{
    VAR_INFO *var = vars + v ;
    ulong n = ( var->no )++ ;
    if ( n >= var->sz ) extend_linkage ( v ) ;
    var->uses [n] = USAGE_USE ;
    var->names [n] = s ;
    var->present = 1 ;
    if (v == 0 && n == 5) {
      BUFFER bf = NULL_buff;
      clear_buffer(&bf, stderr);
      bfprintf(&bf, "capsule_no(%s, %d)\n", s, v);
      output_buffer(&bf, 1);
      free_buffer(&bf);
    }
    return ( n | LINK_EXTERN ) ;
}

and we learn that from all the functions in capsule.c that seem to write
'vars[0].names[5]' and 'vars[0].uses[5]', only capsule_no is actually invoked
(with s = NULL), so it must be the one we are looking for. I have been tracing
and tracing, and I have found that compile_function [../output/compile.c] is
the nexus between the part of the frontend that processes external declarations
and the part that encodes the output TDF capsule (in ../output/). What follows
is a summary of the calls starting from external_declaration [declare.c] and
going down to enc_func_defn [../output/compile.c], which, as its name implies,
is in charge of encoding the body of the function 'operator<< <double> (B &,
const double &)', and it is during its activation when the error is reported.

external_declaration [declare.c]
  clear_templates [instance.c], templ = 0
    copy_template_list [instance.c]
      copy_template_list [instance.c]
        copy_template [instance.c], A &operator<< <double>(B &, const double &)
          define_templ_member [instance.c]
            bind_template [instance.c], tid != NULL_id
              bind_template [instance.c], tid == NULL_id
                copy_object [copy.c], A &operator<< <double>(B&, const double&)
                  pop_namespace [namespace.c]
                  end_function [function.c]
                    ...
                    define_id [identifier.c]
                    compile_function [compile.c], A &operator<< <double>
                      make_tagdef [compile.c], A &operator<< <double>
                        capsule_id [capsule.c], A &operator<< <double>, ret 0
                        ...
                        enc_func_defn [compile.c]
                          ...
                          enc_compound_stmt [stmt.c]
                            [*]
                  end_templ_scope [rewrite.c]
                  end_declarator [namespace.c]
            clear_templates [instance.c], templ = 0

[*] Let us trace this call with more detail, since the problem arises during
    its activation (note the appearance of the message "capsule_no(, 0)"):

enc_compound_stmt, 65 exp_sequence_tag [stmt.c]
  enc_compound_stmt, 84 exp_location_tag
  enc_compound_stmt, 67 exp_decl_stmt_tag
    enc_stmt, 67 exp_decl_stmt_tag [stmt.c]
      enc_stmt, 65 exp_sequence_tag
        enc_compound_stmt, 65 exp_sequence_tag
          enc_compound_stmt, 84 exp_location_tag
            enc_compound_stmt, 73 exp_return_stmt_tag
              enc_stmt, 73 exp_return_stmt_tag, Simple return
                enc_exp, 19 exp_address_tag [exp.c]
                  enc_addr_exp, 17 exp_indir_tag [exp.c]
                    enc_exp, 22 exp_func_id_tag
                      enc_func_id_call
                        ...
                        capsule_id, A &A::operator<< <double>(const C<double>&)
                        ...
                        enc_exp_list
                          enc_exp, 19 exp_address_tag
                            enc_addr_exp, 14 exp_init_tag
                              enc_init_tag, 49 exp_constr_tag
                               [exp_dummy_no written with 6 at init.c line 711]
                                enc_exp, 22 exp_func_id_tag
                                  enc_func_id_call
                                    ...
                                    capsule_id, A::A(const B &)
                                    ...
                                    enc_exp_list
                                      enc_exp, 19 exp_address_tag
                                        enc_addr_exp, 87 exp_dummy_tag
                                          enc_exp, 87 exp_dummy_tag
                                            enc_dummy_exp, n = 6, cnt = 0
                                            enc_dummy_exp: returning
                                          enc_exp: returning
                                        enc_addr_exp: returning
                                      enc_exp: returning
                                      enc_exp, 19 exp_address_tag
                                        enc_addr_exp, 17 exp_indir_tag
                                          enc_exp, 0 exp_identifier_tag
                                            enc_addr_exp, 0 exp_identifier_tag
                                            enc_addr_exp: returning
                                          enc_exp: returning
                                        enc_addr_exp: returning
                                      enc_exp: returning
                                    enc_exp_list: returning
                                  enc_func_id_call: returning
                                enc_exp: returning
                              enc_init_tag: returning
                            enc_addr_exp: returning
                          enc_exp: returning
                          enc_exp, 49 exp_constr_tag
                            enc_exp, 22 exp_func_id_tag
                              enc_func_id_call
                                ...
                                capsule_id, C<double>::C(const double &)
                                ...
                                enc_exp_list
                                  enc_exp, 19 exp_address_tag
                                    enc_addr_exp, 87 exp_dummy_tag
                                      enc_exp, 87 exp_dummy_tag
                                        enc_dummy_exp, n = LINK_NONE, cnt = 0
                                          capsule_no(, 0)
                                        enc_dummy_exp: returning
                                      enc_exp: returning
                                    enc_addr_exp: returning
                                  enc_exp: returning
                                  enc_exp, 19 exp_address_tag
                                    enc_addr_exp, 17 exp_indir_tag
                                      enc_exp, 0 exp_identifier_tag
                                        enc_addr_exp, 0 exp_identifier_tag
                                        enc_addr_exp: returning
                                      enc_exp: returning
                                    enc_addr_exp: returning
                                  enc_exp: returning
                                enc_exp_list
                              enc_func_id_call: returning
                            enc_exp: returning
                          enc_exp: returning
                        enc_exp_list: returning
                      enc_func_id_call: returning
                    enc_exp: returning
                  enc_addr_exp: returning
                enc_exp: returning
              enc_stmt: returning
            enc_compound_stmt: returning
          enc_compound_stmt: returning
        enc_compound_stmt: returning
      enc_stmt: returning
    enc_stmt: returning
  enc_compound_stmt: returning
enc_compound_stmt: returning

The member function 'A::operator<< <double> (const C<double> &)' takes two
arguments and both of them are temporary objects built by user-defined
constructors. In the above call tree, we can recognize clearly each one of the
traces corresponding to the encoding of the calls to each constructor,
'A::A(const B &)' and 'C<double>::C(const double &)'. Each constructor takes
two arguments: a 'this' pointer and a reference to another object. The 'this'
pointer passed to the constructor 'C<double>::C(const double &)' is initialized
to the address of a temporary object of the class 'C<double>', and something is
going wrong during the encoding of this temporary object.

The wrong value (LINK_NONE) passed as second argument to the function
capsule_no comes from the field exp_dummy_no in the function enc_exp
[../output/exp.c] (line 2860). I have been tracing every assigment made to
(instances of) the field exp_dummy_no throughout the frontend, and what is
happening is that: for the constructor 'A::A(const B &)', whose call is built
correctly, the field exp_dummy_no is written with the value 6 (this event is
marked in the call tree above); for the constructor 'C<double>::C(const double
&)', the field exp_dummy_no is not written at all (LINK_NONE is its default
value).

The assignment of the field exp_dummy_no for the constructor 'A::A(const B &)'
is done in the function enc_init_tag [../output/init.c], in the case
exp_constr_tag. The function enc_init_tag is called by enc_addr_exp
[../output/exp.c] in the case exp_init_tag. If we take a look at the call tree
above, in the subtree corresponding to the call to the constructor
'C<double>::C(const double &)', we see that the exp_constr_tag is present,
but exp_address_tag and exp_init_tag (which are the tags that drive the control
flow from enc_exp to enc_init_tag) are missing. The absence of exp_init_tag is
significant, since the constructor MAKE_exp_init is used only in three places
throughout the frontend:

  - inside the function copy_exp [copy.c], with the obvious intention (this
    function is used to instantiate templates);

  - inside the function check_cond [statement.c], but in response to the
    expressions lex_while and lex_for, which are not relevant right now;

  - finally, inside the function make_temporary [initialise.c], which is used
    to build temporary objects.

It is obvious that we are interested in the invocations of make_temporary. I
have written a file almost identical to tok_recur.C, named tok_recur_e.C, which
is compiled without problems, and which differs from tok_recur.C only in that
the call to the member function A::operator<< is made explicitly inside the
function operator<<

template <class T>                                   // tok_recur_e.C, succeeds
inline A &operator << (B &b, const T &c) { return A(b).operator<<(C<T>(c)); }

instead of using an ambiguous expression, as in tok_recur.C,

template <class T>                                   // tok_recur.C, fails
inline A &operator << (B &b, const T &c) { return A(b) << C<T>(c); }

I have been comparing the treatment given to the constructor
'C<double>::C(const double &)' in both programs, and this bug looks similar
in origin to <t4m_bug_func_id>, in the sense that there are certain things
which are tried during the overload resolution, but which are delayed due to
the presence of free template parameters and, later on, during template
instantiation, these things are not properly redone/retried.

We know that the function copy_object [copy.c] is in charge of instantiating
templates. Let us trace the calls to copy_exp [copy.c] from copy_object when
it is generating the instance 'operator<< <double> (B &, const double &)'.
First we will do it for the file that is compiled correctly, tok_recur_e.C,

copy_exp, 65 exp_sequence_tag
  copy_exp, 84 exp_location_tag
  copy_exp, 84 exp_location_tag
    copy_exp, 73 exp_return_stmt_tag
      copy_exp, 81 exp_opn_tag
        apply_nary, 342 lex_func_Hop, cpy = 1
          copy_func_exp, 23 exp_call_tag
            copy_exp, 23 exp_call_tag
              copy_exp, 1 exp_member_tag
                rescan_member, template<class T> A &A::operator<<(const C<T> &)
              copy_exp, 49 exp_constr_tag
                copy_exp, 22 exp_func_id_tag
                  rescan_member, A::A(const B &)
                  copy_func_args, A::A(const B &)
                    copy_exp, 19 exp_address_tag /* Simple argument copy */
                      copy_exp, 87 exp_dummy_tag
                    copy_exp, 19 exp_address_tag /* Simple argument copy */
                      copy_exp, 17 exp_indir_tag
                        copy_exp, 0 exp_identifier_tag
                          rescan_member, b
          copy_exp_list
            copy_exp, 80 exp_op_tag
              expand_type, C<T>
                ...
                instance_type
                  copy_members
                    copy_member, template <class T> class C
                    copy_member, C<double>::c
                    copy_member, C<double>::C(const T &)
                    copy_member, C<double>::~C()
                    copy_member, C &C<double>::operator= (const C &)
              apply_unary, 185 lex_cast, cpy = 1
                copy_exp, 17 exp_indir_tag
                  copy_exp, 0 exp_identifier_tag
                    rescan_member, c
                make_cast_exp
                  convert_reference
                  cast_exp
                    resolve_cast
                    init_direct
                      init_constr, type_compound_tag
                        convert_constr
                          constr_candidates
                          resolve_overload, C<double>::C(const C<double> &)
                                          C<double>::C(const double &) [winner]
                          use_func_id, C<double>::C(const double &)
                            define_template, C<double>::C(const double &)
                          apply_constr
                            apply_func_id, C<double>::C(const double &)
                              cast_args
          make_func_exp, 23 exp_call_tag, rescan = 1
            rescan_func_id, template <class T> A &A::operator<< (const C<T> &)
              rescan_member
            resolve_call, template <class T> A &A::operator<< (const C<T> &)
              deduce_args
                inst_func_deduce
                   instance_func
            use_func_id, A &A::operator<< <double> (const C<double> &)
              define_template
            apply_func_id, A &A::operator<< <double> (const C<double> &)
              cast_args
                make_temporary, t = A, tag(t) = 11, tag(e) = 49
                init_assign, 6 type_ref_tag, t = const C<double> &
                  init_ref_lvalue /* Check base class conversions first */
                  convert_conv_aux
                  init_ref_lvalue
                  init_ref_rvalue
                    find_base_class
                    qualify_type
                    make_temporary, t = const C<double>, tag(t)=11, tag(e)=49
                    cast_class_class
                  make_ref_init
 
and with this (except for a call to init_assign that is probably called from
find_return_exp), all invocations return up to copy_object. [Note: in case we
want a wider portrait of this situation, the path from external_declaration
down to copy_object in tok_recur_e.C is identical to that we showed before for
tok_recur.C, and this subtree is also followed by a call to compile_function.]

In the above call trace, we must pay attention to two subtrees. The first
starts from

              apply_unary, 185 lex_cast, cpy = 1

[operator.c] and it is the trace of the call to constructor 'C<double>::C(const
double &)'. In fact, the subexpression 'C<T>(c)' that appears in the program is
an explicit cast from the type 'const T &' to the type 'C<T>', hence the name
of the lexical operator.

The second subtree starts from

            apply_func_id, A &A::operator<< <double> (const C<double> &)

[function.c] which, being called from make_func_exp [function.c], builds the
invocation to the member funcion 'A::operator<<'. To do this, it is necessary
to build two temporary objects, one of type 'A', another of type 'C<double>',
and pass them by reference. We see that this is done by cast_args [function.c]
calling make_temporary [initialise.c] on both types.

Now, we are going to trace the calls to copy_exp from copy_object in the case
of the program whose compilation fails, tok_recur.C,

copy_exp, 65 exp_sequence_tag
  copy_exp, 84 exp_location_tag
  copy_exp, 67 exp_decl_stmt_tag
    copy_exp, 65 exp_sequence_tag
      copy_exp, 84 exp_location_tag
        copy_exp, 73 exp_return_stmt_tag
          copy_exp, 19 exp_address_tag
            copy_exp, 17 exp_indir_tag
              copy_exp, 22 exp_func_id_tag
                rescan_member, A &A::operator<< <T> (const C<T> &)
                  rescan_member,template<class T> A &A::operator<<(const C<T>&)
                    apply_template
                      apply_func_templ
                        instance_func
                          expand_name [../parse/hash.c]
                            expand_type, template <class T> A & (const C<T> &)
                              ...
                              instance_type
                                copy_members
                                  copy_member,template <class T> class C
                                  copy_member,C<double>::c
                                  copy_member,C<double>::C(const T &)
                                  copy_member,C<double>::~C()
                                  copy_member,C &C<double>::operator=(const C&)
                    define_template,A &A::operator<< <double>(const C<double>&)
                copy_func_args, A &A::operator<< <double>(const C<double> &)
                  copy_exp, 19 exp_address_tag /* Simple argument copy */
                    copy_exp, 14 exp_init_tag
                      copy_exp, 49 exp_constr_tag
                        copy_exp, 22 exp_func_id_tag
                          rescan_member, A::A(const B &)
                          copy_func_args, A::A(const B &)
                            copy_exp, 19 exp_address_tag /* Simple arg. copy */
                              copy_exp, 87 exp_dummy_tag
                            copy_exp, 19 exp_address_tag /* Simple arg. copy */
                              copy_exp, 17 exp_indir_tag
                                copy_exp, 0 exp_identifier_tag
                                  rescan_member, b
                  copy_exp, 80 exp_op_tag /* Do implicit argument conversion */
                    expand_type, C<T>
                      ...
                      instance_type
                    apply_unary, 185 lex_cast, cpy = 1
                      copy_exp, 17 exp_indir_tag
                        copy_exp, 0 exp_identifier_tag
                          rescan_member, c
                      make_cast_exp
                        convert_reference
                        cast_exp
                          resolve_cast
                          init_direct
                            init_constr, type_compound_tag
                              convert_constr
                                constr_candidates
                                resolve_overload,C<double>::C(const C<double>&)
                                          C<double>::C(const double &) [winner]
                                use_func_id, C<double>::C(const double &)
                                  define_template, C<double>::C(const double &)
                                apply_constr
                                  apply_func_id, C<double>::C(const double &)
                                    cast_args
                  init_assign, 11 type_compound_tag, t = C<double>, e = c
                    init_direct, t = C<double>, e = c
                      init_constr, 11 type_compound_tag (the same arguments)
                        convert_constr t = C<double>, args = List(e)
                          constr_candidates
                          resolve_overload [C<double>::C(const C<double> &)]
                          use_func_id, C<double>::C(const C<double> &)
                          apply_constr
                            apply_trivial_func, n = 2 DEFAULT_COPY
                              convert_reference [convert.c]
                                ...
                              convert_class [assign.c]
                                ...

and with this all invocations return up to copy_object.

There are two important things in this tree. First, the subtree starting from

                    apply_unary, 185 lex_cast, cpy = 1

is identical to that of the file that is compiled correctly (tok_recur_e.C).
But, on the other hand, we do not see any call to make_temporary. There is a
misterious phenomenon which gives us a hint about what is happening: the
respective calls to the function init_assign [initialise.c] in each file.
This function is in charge of generating the initialization of the temporary
object. In the file whose compilation succeeds (tok_recur_e.C), we have a
subtree that starts from

                init_assign, 6 type_ref_tag, t = const C<double> &

called from cast_args [function.c], where type_ref_tag is the tag of the type
of the argument of init_assign. But in the file whose compilation fails
(tok_recur.C), we have instead

                  init_assign, 11 type_compound_tag, t = C<double>, e = c

called from copy_func_args [copy.c], and this makes the compiler generate a
call to the copy constructor 'C<double>::C(const C<double> &)'. To understand
the cause of the difference between the tags, let us trace the call to
apply_func_id [function.c] on the function 'A::operator<< <T> (const C<T> &)'
in the compilation of tok_recur.C; this call happens sooner in the compilation,
after the overload resolution that takes place inside 'template <class T>
operator<< (B &, const T &)',

use_func_id, A &A::operator<< <T> (const C<T> &)
  define_template /* Ignore template dependent instantiations */
apply_func_id, A &A::operator<< <T> (const C<T> &)
  cast_args
    make_temporary, t = A, tag(t) = 11, tag(e) = 49
    init_assign, t = const C<T> &, type_ref_tag
      init_ref_lvalue /* Check base class conversions first */
      convert_conv_aux, cast = CAST_IMPLICIT [construct.c]
        dependent_conv [template.c], returns 1
        cast_templ_type [cast.c]

The function cast_templ_type [cast.c] creates a lex_implicit operator that
contains, as a subexpression, the lex_cast operator that was present in the
source program; init_assign [initialise.c] returns immediately in the
conditional 'if ( eq_type ( r, t ) ) break ;'. The function cast_templ_type
puts the destination type of the cast ('const C<T> &', in the present case)
inside the lex_implicit operator. It is assumed that this implicit cast must
be reprocessed inside the function copy_func_args [copy.c], finishing what
cast_args [function.c] left undone. But there is a subtle bug in function
copy_func_args.

The problem is as follows. First, the function implicit_cast_exp [copy.c] is
called to check if the expression 'e' is an implicit cast (as it really is).
The function implicit_cast_exp returns null if its argument is not an
implicit cast; otherwise it returns the expression being cast, that is, the
original lex_cast expression that was put inside the lex_implicit operator:

	EXP e = DEREF_exp ( HEAD_list ( p ) ) ;
	EXP a = implicit_cast_exp ( e ) ;
	if ( !IS_NULL_exp ( a ) ) {
	    /* Do implicit argument conversion */
	    TYPE t ;
	    ERROR err = NULL_err ;
	    a = copy_exp ( a, NULL_type, NULL_type ) ;
	    t = DEREF_type ( exp_type ( a ) ) ;
	    e = init_assign ( t, cv_none, a, &err ) ;

It is ok that copy_exp is called with the argument 'a' without the lex_implicit
operator, since this was the original expression found in the program, and we
have already seen that the trace starting from "copy_exp, 80 exp_op_tag"
(just before "apply_unary, 185 lex_cast, cpy = 1") is the same for both files.
But the destination type of the cast, 'const C<T> &', which is the type of the
formal parameter of the function that is being applied, is lost when the
lex_implicit operator is discarded, and the type of the actual argument,
t = 'C<double>' is used instead. That is why init_assign [initialise.c] ends up
generating a call to the copy constructor: it thinks that the type of the
actual argument and the type of the formal parameters are the same type
'C<double>'.

Then, what we have to fix is the type 't' that is passed to the call

	    e = init_assign ( t, cv_none, a, &err ) ;

The type 't' should be taken from the expression 'e' (and not from 'a', as it
happens now). Recall that the implicit cast expression 'e' was built in this
way inside the function cast_templ_type [cast.c],

EXP cast_templ_type
    PROTO_N ( ( t, a, cast ) )
    PROTO_T ( TYPE t X EXP a X unsigned cast )
{
    /* etc.
      t = const C<T> &, op = lex_implicit
     */
    MAKE_exp_op ( t, op, a, NULL_exp, e ) ;
    return ( e ) ;
}

and so the type 't' can be recovered just changing the line

	    t = DEREF_type ( exp_type ( a ) ) ;

for

	    t = DEREF_type ( exp_type ( e ) ) ;
	    t = expand_type ( t, 1 ) ;

It is necessary to expand the type, since normally it will be dependent on the
parameters of the template we are currently instantiating. In the example
tok_recur.C, before expansion 't' is 'const C<T> &', and after expansion 't' is
'const C<double> &'.

The solution works, and it also works with two variants that caused the same
error message:

  - tok_recur_f.C, where the template function 'A::operator<<' is not a member
                   function,

  - tok_recur_t.C, where the classes A and B are also template classes.