4.3.1.7.8.2.3.2. type_info的后处理
4.3.1.7.8.2.3.2.1. 安装基本类型type_info对象
前端调用函数finish_file最后完成其工作,并为后端产生RTL形式的代码树。也在这个函数中,逐个为unemitted_tinfo_decls中保存的待后处理的伪type_info,构建真正的对象。不过在创建工作之前,首先要安装基本类型的type_info对象如下。
1353 void
1354 emit_support_tinfos (void) in rtti.c
1355 {
1356 static tree *const fundamentals[] =
1357 {
1358 &void_type_node,
1359 &boolean_type_node,
1360 &wchar_type_node,
1361 &char_type_node, &signed_char_type_node, &unsigned_char_type_node,
1362 &short_integer_type_node, &short_unsigned_type_node,
1363 &integer_type_node, &unsigned_type_node,
1364 &long_integer_type_node, &long_unsigned_type_node,
1365 &long_long_integer_type_node, &long_long_unsigned_type_node,
1366 &float_type_node, &double_type_node, &long_double_type_node,
1367 0
1368 };
1369 int ix;
1370 tree bltn_type, dtor;
1371
1372 push_nested_namespace (abi_node);
1373 bltn_type = xref_tag (class_type,
1374 get_identifier ("__fundamental_type_info"),
1375 true, false);
1376 pop_nested_namespace (abi_node);
1377 if (!COMPLETE_TYPE_P (bltn_type))
1378 return;
1379 dtor = TREE_VEC_ELT (CLASSTYPE_METHOD_VEC (bltn_type), 1);
1380 if (DECL_EXTERNAL (dtor))
1381 return;
1382 doing_runtime = 1;
1383 for (ix = 0; fundamentals[ix]; ix++)
1384 {
1385 tree bltn = *fundamentals[ix];
1386 tree bltn_ptr = build_pointer_type (bltn);
1387 tree bltn_const_ptr = build_pointer_type
1388 (build_qualified_type (bltn, TYPE_QUAL_CONST));
1389 tree tinfo;
1390
1391 tinfo = get_tinfo_decl (bltn);
1392 TREE_USED (tinfo) = 1;
1393 TREE_SYMBOL_REFERENCED (DECL_ASSEMBLER_NAME (tinfo)) = 1;
1394
1395 tinfo = get_tinfo_decl (bltn_ptr);
1396 TREE_USED (tinfo) = 1;
1397 TREE_SYMBOL_REFERENCED (DECL_ASSEMBLER_NAME (tinfo)) = 1;
1398
1399 tinfo = get_tinfo_decl (bltn_const_ptr);
1400 TREE_USED (tinfo) = 1;
1401 TREE_SYMBOL_REFERENCED (DECL_ASSEMBLER_NAME (tinfo)) = 1;
1402 }
1403 }
基本类型的type_info对象,其类型是__fundamental_type_info,其声明在cxxabiv1名字空间里,其定义则在文件tinfo2.cc中。如果在编译GCC时,包含了tinfo2.cc,而不仅仅只是cxxabi.h,那么上面1373行的bltn_type将是一个完整的RECORD_TYPE节点,将顺利通过1377行和1380行的条件,为所有fundamentals成员构建type_info对象。注意这里构建的还是伪对象。另在1382行,doing_runtime被设为1,表明有运行时库支持。
4.3.1.7.8.2.3.2.2. 构建真实type_info对象
下面函数中的参数decl就是待处理的伪type_info。
1433 bool
1434 emit_tinfo_decl (tree decl) in rtti.c
1435 {
1436 tree type = TREE_TYPE (DECL_NAME (decl));
1437 bool non_public;
1438 int in_library = typeinfo_in_lib_p (type);
1439 tree var_desc, var_init;
1440
1441 my_friendly_assert (unemitted_tinfo_decl_p (decl), 20030307);
1442
1443 import_export_tinfo (decl, type, in_library);
1444 if (DECL_REALLY_EXTERN (decl) || !DECL_NEEDED_P (decl))
1445 return false;
1446
1447 if (!doing_runtime && in_library)
1448 return false;
1449
1450 non_public = false;
1451 var_desc = get_pseudo_ti_desc (type);
1452 var_init = get_pseudo_ti_init (type, var_desc, &non_public);
1453
1454 DECL_EXTERNAL (decl) = 0;
1455 TREE_PUBLIC (decl) = !non_public;
1456 if (non_public)
1457 DECL_COMDAT (decl) = 0;
1458
1459 DECL_INITIAL (decl) = var_init;
1460 mark_used (decl);
1461 cp_finish_decl (decl, var_init, NULL_TREE, 0);
1462 /* cp_finish_decl will have dealt with linkage. */
1463
1464 /* Say we've dealt with it. */
1465 TREE_TYPE (DECL_NAME (decl)) = NULL_TREE;
1466
1467 return true;
1468 }
在构建真实type_info对象前,编译器需要判断这个对象是否真的需要构建。不必要的构建不但会降低编译效率,也会使生成的代码膨胀。
下面的函数实际上是判断是否要求基本类型的type_info对象。因为基本类型的type_info对象已经由编译器自动构建,不存在构建、还是不构建的问题。
965 static bool
966 typeinfo_in_lib_p (tree type) in rtti.c
967 {
968 /* The typeinfo objects for `T*' and `const T*' are in the runtime
969 library for simple types T. */
970 if (TREE_CODE (type) == POINTER_TYPE
971 && (cp_type_quals (TREE_TYPE (type)) == TYPE_QUAL_CONST
972 || cp_type_quals (TREE_TYPE (type)) == TYPE_UNQUALIFIED))
973 type = TREE_TYPE (type);
974
975 switch (TREE_CODE (type))
976 {
977 case INTEGER_TYPE:
978 case BOOLEAN_TYPE:
979 case CHAR_TYPE:
980 case REAL_TYPE:
981 case VOID_TYPE:
982 return true;
983
984 default:
985 return false;
986 }
987 }
下面的函数则是判断type_info所对应的对象是内部的,还是来自外部的。要是来自外部,那么很可能这个type_info对象(注意这可是全局对象)已在别的编译单元产生。因而前端告诉后端,只生成必要的对象。而剩下的引用将由链接器解决。
1729 void
1730 import_export_tinfo (tree decl, tree type, bool is_in_library) in decl2.c
1731 {
1732 if (DECL_INTERFACE_KNOWN (decl))
1733 return;
1734
1735 if (IS_AGGR_TYPE (type))
1736 import_export_class (type);
1737
1738 if (IS_AGGR_TYPE (type) && CLASSTYPE_INTERFACE_KNOWN (type)
1739 && TYPE_POLYMORPHIC_P (type)
1740 /* If -fno-rtti, we're not necessarily emitting this stuff with
1741 the class, so go ahead and emit it now. This can happen when
1742 a class is used in exception handling. */
1743 && flag_rtti)
1744 {
1745 DECL_NOT_REALLY_EXTERN (decl) = !CLASSTYPE_INTERFACE_ONLY (type);
1746 DECL_COMDAT (decl) = 0;
1747 }
1748 else
1749 {
1750 DECL_NOT_REALLY_EXTERN (decl) = 1;
1751 DECL_COMDAT (decl) = 1;
1752 }
1753
1754 /* Now override some cases. */
1755 if (flag_weak)
1756 DECL_COMDAT (decl) = 1;
1757 else if (is_in_library)
1758 DECL_COMDAT (decl) = 0;
1759
1760 DECL_INTERFACE_KNOWN (decl) = 1;
1761 }
如果type_info所对应的对象是类,那么需要下面的函数来进行一些处理。这个函数主要是为类设置CLASSTYPE_INTERFACE_KNOWN,如果这个域是true,表明我们已经确定项虚函数表、tinfo对象等类簿记数据是否在本编译单元生成。对于定义了虚函数的类,其type_info对象将被保存在虚函数表里(只有这样的类编译器才主动为其产生type_info;否则需要dynamic_cast操作符来触发,而且生成的tinfo对象只由dynamic_cast保存)。
1484 static void
1485 import_export_class (tree ctype) in decl2.c
1486 {
1487 /* -1 for imported, 1 for exported. */
1488 int import_export = 0;
1489
1490 /* It only makes sense to call this function at EOF. The reason is
1491 that this function looks at whether or not the first non-inline
1492 non-abstract virtual member function has been defined in this
1493 translation unit. But, we can't possibly know that until we've
1494 seen the entire translation unit. */
1495 my_friendly_assert (at_eof, 20000226);
1496
1497 if (CLASSTYPE_INTERFACE_KNOWN (ctype))
1498 return;
1499
1500 /* If MULTIPLE_SYMBOL_SPACES is defined and we saw a #pragma interface,
1501 we will have CLASSTYPE_INTERFACE_ONLY set but not
1502 CLASSTYPE_INTERFACE_KNOWN. In that case, we don't want to use this
1503 heuristic because someone will supply a #pragma implementation
1504 elsewhere, and deducing it here would produce a conflict. */
1505 if (CLASSTYPE_INTERFACE_ONLY (ctype))
1506 return;
1507
1508 if (lookup_attribute ("dllimport", TYPE_ATTRIBUTES (ctype)))
1509 import_export = -1;
1510 else if (lookup_attribute ("dllexport", TYPE_ATTRIBUTES (ctype)))
1511 import_export = 1;
1512
1513 /* If we got -fno-implicit-templates, we import template classes that
1514 weren't explicitly instantiated. */
1515 if (import_export == 0
1516 && CLASSTYPE_IMPLICIT_INSTANTIATION (ctype)
1517 && ! flag_implicit_templates)
1518 import_export = -1;
1519
1520 /* Base our import/export status on that of the first non-inline,
1521 non-pure virtual function, if any. */
1522 if (import_export == 0
1523 && TYPE_POLYMORPHIC_P (ctype))
1524 {
1525 tree method = CLASSTYPE_KEY_METHOD (ctype);
1526 if (method)
1527 import_export = (DECL_REALLY_EXTERN (method) ? -1 : 1);
1528 }
1529
1530 #ifdef MULTIPLE_SYMBOL_SPACES
1531 if (import_export == -1)
1532 import_export = 0;
1533 #endif
1534
1535 if (import_export)
1536 {
1537 SET_CLASSTYPE_INTERFACE_KNOWN (ctype);
1538 CLASSTYPE_INTERFACE_ONLY (ctype) = (import_export < 0);
1539 }
1540 }
上面1523行的TYPE_POLYMORPHIC_P,如果非0,就表示该类定义有虚函数。另外,1525行的CLASSTYPE_KEY_METHOD返回的是,第一个非内联、非纯虚函数。如果我们已经看到了这个类的定义,毫无疑问,虚函数表、tinfo对象这样的类簿记数据是需要在被编译单元产生的。另外具有“dllexport”属性的类,因为明确声明为导出的,这些数据,如果有的话,需要在本编译单元产生。
而对于普通的类,即没有虚函数表,亦不保存tinfo对象,编译器尽量让其tinfo对象在编译单元间共享。为此,在import_export_class的1751行,设置DECL_COMDAT。
回到emit_tinfo_decl,在1444行,DECL_REALLY_EXTERN的定义为:
2856 #define DECL_REALLY_EXTERN(NODE) / in cp-tree.h
2857 (DECL_EXTERNAL (NODE) && ! DECL_NOT_REALLY_EXTERN (NODE))
当tinfo被认定为在外部定义的,或者是基本类型的tinfo,但运行时支持没有(1447行条件)那么我们就不再趟这趟浑水。否则,就要往下构建初始化真实tinfo对象的代码。注意下面函数的参数var_desc是从1451行,由get_pseudo_ti_desc取回的伪tinfo对象。
995 static tree
996 get_pseudo_ti_init (tree type, tree var_desc, bool *non_public_p) in rtti.c
997 {
998 my_friendly_assert (at_eof, 20021120);
999 switch (TREE_CODE (type))
1000 {
1001 case OFFSET_TYPE:
1002 return ptm_initializer (var_desc, type, non_public_p);
1003 case POINTER_TYPE:
1004 return ptr_initializer (var_desc, type, non_public_p);
1005 case ENUMERAL_TYPE:
1006 return generic_initializer (var_desc, type);
1007 break;
1008 case FUNCTION_TYPE:
1009 return generic_initializer (var_desc, type);
1010 break;
1011 case ARRAY_TYPE:
1012 return generic_initializer (var_desc, type);
1013 break;
1014 case UNION_TYPE:
1015 case RECORD_TYPE:
1016 if (TYPE_PTRMEMFUNC_P (type))
1017 return ptm_initializer (var_desc, type, non_public_p);
1018 else if (var_desc == class_desc_type_node)
1019 {
1020 if (!COMPLETE_TYPE_P (type))
1021 /* Emit a non-public class_type_info. */
1022 *non_public_p = true;
1023 return class_initializer (var_desc, type, NULL_TREE);
1024 }
1025 else if (var_desc == si_class_desc_type_node)
1026 {
1027 tree base_binfos = BINFO_BASETYPES (TYPE_BINFO (type));
1028 tree base_binfo = TREE_VEC_ELT (base_binfos, 0);
1029 tree tinfo = get_tinfo_ptr (BINFO_TYPE (base_binfo));
1030 tree base_inits = tree_cons (NULL_TREE, tinfo, NULL_TREE);
1031
1032 return class_initializer (var_desc, type, base_inits);
1033 }
1034 else
1035 {
1036 int hint = class_hint_flags (type);
1037 tree binfo = TYPE_BINFO (type);
1038 int nbases = BINFO_N_BASETYPES (binfo);
1039 tree base_binfos = BINFO_BASETYPES (binfo);
1040 tree base_accesses = BINFO_BASEACCESSES (binfo);
1041 tree base_inits = NULL_TREE;
1042 int ix;
1043
1044 /* Generate the base information initializer. */
1045 for (ix = nbases; ix--;)
1046 {
1047 tree base_binfo = TREE_VEC_ELT (base_binfos, ix);
1048 tree base_init = NULL_TREE;
1049 int flags = 0;
1050 tree tinfo;
1051 tree offset;
1052
1053 if (TREE_VEC_ELT (base_accesses, ix) == access_public_node)
1054 flags |= 2;
1055 tinfo = get_tinfo_ptr (BINFO_TYPE (base_binfo));
1056 if (TREE_VIA_VIRTUAL (base_binfo))
1057 {
1058 /* We store the vtable offset at which the virtual
1059 base offset can be found. */
1060 offset = BINFO_VPTR_FIELD (base_binfo);
1061 offset = convert (sizetype, offset);
1062 flags |= 1;
1063 }
1064 else
1065 offset = BINFO_OFFSET (base_binfo);
1066
1067 /* Combine offset and flags into one field. */
1068 offset = cp_build_binary_op (LSHIFT_EXPR, offset,
1069 build_int_2 (8, 0));
1070 offset = cp_build_binary_op (BIT_IOR_EXPR, offset,
1071 build_int_2 (flags, 0));
1072 base_init = tree_cons (NULL_TREE, offset, base_init);
1073 base_init = tree_cons (NULL_TREE, tinfo, base_init);
1074 base_init = build_constructor (NULL_TREE, base_init);
1075 TREE_HAS_CONSTRUCTOR (base_init) = 1;
1076 base_inits = tree_cons (NULL_TREE, base_init, base_inits);
1077 }
1078 base_inits = build_constructor (NULL_TREE, base_inits);
1079 TREE_HAS_CONSTRUCTOR (base_inits) = 1;
1080 base_inits = tree_cons (NULL_TREE, base_inits, NULL_TREE);
1081 /* Prepend the number of bases. */
1082 base_inits = tree_cons (NULL_TREE,
1083 build_int_2 (nbases, 0), base_inits);
1084 /* Prepend the hint flags. */
1085 base_inits = tree_cons (NULL_TREE,
1086 build_int_2 (hint, 0), base_inits);
1087
1088 return class_initializer (var_desc, type, base_inits);
1089 }
1090 break;
1091
1092 default:
1093 return generic_initializer (var_desc, type);
1094 }
1095 }
我们以多继承类作为例子来看一下真实tinfo对象的构建。下面的函数首先确定描述类继承树特性的暗示标识。
936 static int
937 class_hint_flags (tree type) in rtti.c
938 {
939 int hint_flags = 0;
940
941 dfs_walk (TYPE_BINFO (type), dfs_class_hint_mark, NULL, &hint_flags);
942 dfs_walk (TYPE_BINFO (type), dfs_class_hint_unmark, NULL, NULL);
943
944 return hint_flags;
945 }
函数dfs_walk将以深度优先的后序遍历来访问类的继承树,这个函数的细节我们在后面再来看。对于该类的每个基类,dfs_class_hint_mark按从最基本的基类到最后的派生类的次序执行。注意到对于非虚拟派生类hint是1,而对于包含虚拟基类的类则是3。
899 static tree
900 dfs_class_hint_mark (tree binfo, void *data) in rtti.c
901 {
902 tree basetype = BINFO_TYPE (binfo);
903 int *hint = (int *) data;
904
905 if (TREE_VIA_VIRTUAL (binfo))
906 {
907 if (CLASSTYPE_MARKED (basetype))
908 *hint |= 1;
909 if (CLASSTYPE_MARKED2 (basetype))
910 *hint |= 2;
911 SET_CLASSTYPE_MARKED2 (basetype);
912 }
913 else
914 {
915 if (CLASSTYPE_MARKED (basetype) || CLASSTYPE_MARKED2 (basetype))
916 *hint |= 1;
917 SET_CLASSTYPE_MARKED (basetype);
918 }
919 return NULL_TREE;
920 }
多继承类所对应的type_info对象的类型是__vmi_class_type_info。在cxxabi.h文件中可以看到,这个类含有一个__base_class_type_info数组用于描述每个基类。1045行的FOR循环就是初始化这个数组。base_init是用于构建类对象的初始值列表,注意这个链节点的次序正好与初始化的次序相反。还有,cxxabi.h所定义的__vmi_class_type_info的构造函数,并没有对其包含的__base_class_type_info数组进行初始化。但在这里,编译器利用职权之便绕过构造函数,初始化了这个数组,当然前提是这个初始化列表严格匹配类成员出现的次序。
类型__base_class_type_info具有2个数据成员,一个是指向基类tinfo的指针,另一个是综合基类偏移及信息的标识(__offset_flags)。对于指向基类tinfo的指针,其初始值由下面函数获取,是其基类tinfo对象的地址。
380 static tree
381 get_tinfo_ptr (tree type) in rtti.c
382 {
383 tree decl = get_tinfo_decl (type);
384
385 mark_used (decl);
386 return build_nop (type_info_ptr_type,
387 build_address (decl));
388 }
对于基类的binfo对象,域BINFO_OFFSET或BINFO_VPTR_FIELD(对于虚拟基类而言)记录了该基类相对于所考查派生类的偏移。那么,明显地,对于__offset_flags,其高24位记录了这个偏移,其低8位则表示其是否为公有派生及虚拟基类。从上面的代码,可以看出,类__vmi_class_type_info的第一个数据成员是class_hint_flags找出标识,第二个成员则是基类数目,第三个成员就是__base_class_type_info数组。准备好了初始化列表后,函数class_initializer构建CONSTRUCTOR节点来表示这个构造函数调用表达式。
951 static tree
952 class_initializer (tree desc, tree target, tree trail) in rtti.c
953 {
954 tree init = tinfo_base_init (desc, target);
955
956 TREE_CHAIN (init) = trail;
957 init = build_constructor (NULL_TREE, init);
958 TREE_HAS_CONSTRUCTOR (init) = TREE_CONSTANT (init) = TREE_STATIC (init) = 1;
959 return init;
960 }
不过,到了这里,初始化列表其实还没完全准备好。因为与类相关的tinfo类型都是从__class_type_info派生的。根据C++的规范,基类的初始化要在派生类的数据成员之前,而且对于具有虚函数的类,还有初始化虚函数表的问题,而tinfo类型正好都有虚函数。基类初始化要从最底层的基类开始,这里最底层的type_info具有一个“const char*”数据成员,那么745行的代码块为其构建了定义和初始值。
738 static tree
739 tinfo_base_init (tree desc, tree target) in rtti.c
740 {
741 tree init = NULL_TREE;
742 tree name_decl;
743 tree vtable_ptr;
744
745 {
746 tree name_name;
747
748 /* Generate the NTBS array variable. */
749 tree name_type = build_cplus_array_type
750 (build_qualified_type (char_type_node, TYPE_QUAL_CONST),
751 NULL_TREE);
752 tree name_string = tinfo_name (target);
753
754 name_name = mangle_typeinfo_string_for_type (target);
755 name_decl = build_lang_decl (VAR_DECL, name_name, name_type);
756
757 DECL_ARTIFICIAL (name_decl) = 1;
758 TREE_READONLY (name_decl) = 1;
759 TREE_STATIC (name_decl) = 1;
760 DECL_EXTERNAL (name_decl) = 0;
761 TREE_PUBLIC (name_decl) = 1;
762 import_export_tinfo (name_decl, target, typeinfo_in_lib_p (target));
763 /* External name of the string containing the type's name has a
764 special name. */
765 SET_DECL_ASSEMBLER_NAME (name_decl,
766 mangle_typeinfo_string_for_type (target));
767 DECL_INITIAL (name_decl) = name_string;
768 mark_used (name_decl);
769 pushdecl_top_level_and_finish (name_decl, name_string);
770 }
771
772 vtable_ptr = TINFO_VTABLE_DECL (desc);
773 if (!vtable_ptr)
774 {
775 tree real_type;
776
777 push_nested_namespace (abi_node);
778 real_type = xref_tag (class_type, TINFO_REAL_NAME (desc),
779 true, false);
780 pop_nested_namespace (abi_node);
781
782 if (!COMPLETE_TYPE_P (real_type))
783 {
784 /* We never saw a definition of this type, so we need to
785 tell the compiler that this is an exported class, as
786 indeed all of the __*_type_info classes are. */
787 SET_CLASSTYPE_INTERFACE_KNOWN (real_type);
788 CLASSTYPE_INTERFACE_ONLY (real_type) = 1;
789 }
790
791 vtable_ptr = get_vtable_decl (real_type, /*complete=*/1);
792 vtable_ptr = build_unary_op (ADDR_EXPR, vtable_ptr, 0);
793
794 /* We need to point into the middle of the vtable. */
795 vtable_ptr = build
796 (PLUS_EXPR, TREE_TYPE (vtable_ptr), vtable_ptr,
797 size_binop (MULT_EXPR,
798 size_int (2 * TARGET_VTABLE_DATA_ENTRY_DISTANCE),
799 TYPE_SIZE_UNIT (vtable_entry_type)));
800 TREE_CONSTANT (vtable_ptr) = 1;
801
802 TINFO_VTABLE_DECL (desc) = vtable_ptr;
803 }
804
805 init = tree_cons (NULL_TREE, vtable_ptr, init);
806
807 init = tree_cons (NULL_TREE, decay_conversion (name_decl), init);
808
809 init = build_constructor (NULL_TREE, nreverse (init));
810 TREE_HAS_CONSTRUCTOR (init) = TREE_CONSTANT (init) = TREE_STATIC (init) = 1;
811 init = tree_cons (NULL_TREE, init, NULL_TREE);
812
813 return init;
814 }
前面看到伪tinfo类型是没有构建vatble的。如果没有vtable,表明真实的tinfo对象还没有构建过。注意778行的TINFO_REAL_NAME保存的是真实tinfo对象类型的名字。而xref_tag的第三个实参是true,它实际在__cxxabiv1名字空间中查找这个指定的类型。Vtable的构建是个相当复杂的过程,我们以后再来看这个过程。这里,只要知道vtable的索引并不是从0开始的,编译器需要vtable的开头2项来放置tinfo对象,及该类作为基类到该考虑派生类基址的偏移。因此vtable指针指向的是vtable的第3项。另外,因为tinfo对象都是类的静态成员,因此在810行设置TREE_STATIC,表明应该为其分配静态内存。