Delay non-complete class type resolution up to end of corpus reading

From the DWARF emitted by GCC 4.4.7 for libstdc++ we encountered an
interesting construct.

A non-complete version of std::runtime_error is declared in
libstdc++-v3/src/functexcept.cc and is represented in DWARF as:

     [ 37344]      class_type
		    name                 (strp) "runtime_error"
		    declaration          (flag)

Then a bit later, that *non-complete* class is used as a base class
for a class, *without* being fully defined!  This shouldn't happen
but, well, it does:

     [ 3b3a1]    class_type
		  specification        (ref4) [ 3733e]
		  byte_size            (data1) 16
		  decl_file            (data1) 5
		  decl_line (data1) 141
		  containing_type      (ref4) [ 3734a]
		  sibling              (ref4) [3b405]
     [ 3b3b1]      inheritance
		   type                 (ref4) [ 37344] <---- here.

The thing is that, later, in another translation unit
(libstdc++-v3/src/stdexcept.cc), that same class is defined fully:

     [ 7e9f9]      class_type
		   name                 (strp) "runtime_error"
		   declaration          (flag)
[...]

     [ 80c95]    class_type
		 specification        (ref4) [ 7e9f9]
		 byte_size            (data1) 16
		 decl_file            (data1) 4
		 decl_line            (data1) 108
		 containing_type      (ref4) [ 7e9ff]
		 sibling              (ref4) [ 80d2b]
		 [...] <---------- and the definition goes here.

But then you see that the DIE offset of the "version" of the
runtime_error class that is "defined" libstdc++-v3/src/stdexcept.cc in
is different from the version that is only declared in
libstdc++-v3/src/functexcept.cc.  But virtue of the "One Definition
Rule", we can assume that they designate the same type.  But still,
runtime_error should have been defined in
libstdc++-v3/src/stdexcept.cc.  Anyhow, libabigail needs to be able to
handle this.  That is, it needs to wait until the entire ABI corpus is
loaded from DWARF, then lookup the definition of all the non-complete
types we have encountered.

And then only after that non-complete type resolution has taken place,
we can proceed with type canonicalizing, rather than doing it after
the loading of each translation unit like what we were doing
previously.

This is what this patch does.

	* include/abg-fwd.h (lookup_type_in_corpus): Declare new function.
	* src/abg-corpus.cc (lookup_type_in_corpus): Define new function
	here.
	* include/abg-ir.h (function_types_type): Declare new typedef.
	(translation_unit::get_canonical_function_type): Remove member function.
	(translation_unit::bind_function_type_life_time): Declare new
	member function.
	(classes_type): New typedef.
	* src/abg-ir.cc
	(translation_unit::priv::canonical_function_types_): Remove data
	member.
	(translation_unit::priv::function_types): New data member.
	(translation_unit::get_canonical_function_type): Remove this
	function definition.
	(translation_unit::bind_function_type_life_time): New function
	definition.
	(lookup_node_in_scope): Ensure that the type returned is
	complete.
	* src/abg-dwarf-reader.cc (string_classes_map): New typedef.
	(read_context::decl_only_classes_map_): New data member.
	(read_context::declaration_only_classes): New accessor.
	(read_context::{maybe_schedule_declaration_only_class_for_resolution,
	is_decl_only_class_scheduled_for_resolution,
	resolve_declaration_only_classes, current_elf_file_is_executable,
	current_elf_file_is_dso}): Define new member functions.
	(read_context::clear_per_translation_unit_data): Do not clear the
	data structures that associate DIEs to decls/types or that contain
	the types to canonicalize here.  Rather, clear them ...
	(read_context::clear_per_corpus_data): ... here instead.
	(read_context::build_translation_unit_and_add_to_ir): Do not
	perform late type canonicalizing here.  Rather, do it ...
	(read_debug_info_into_corpus): ... here instead.  And before that,
	call read_context::clear_per_corpus_data() and the new
	read_context::resolve_declaration_only_classes() here.
	(build_class_type_and_add_to_ir): Schedule the non-complete types
	for resolution to complete types.  Assert that base classes that
	are non-complete are scheduled to be completed.
	(build_function_decl): Do not try to canonicalize function types
	this early, systematically.  Now, all the non-complete types needs
	to be completed before starting canonicalizing.  So let function
	types go through the normal processes of deciding when to
	canonicalize them.  But then, bind the life time of the function
	type to the life time of the current translation unit.
	(maybe_canonicalize_type): If a class type is non-complete,
	schedule it for late canonicalizing.
	* src/abg-hash.cc (class_decl:#️⃣:operator()(const class_decl&)
	const): During hashing, a base class should be complete.
	* src/abg-reader.cc
	(read_context::clear_per_translation_unit_data): Do not clear
	id/xml node, and type maps here.  Rather, clear it ...
	(read_context::clear_per_corpus_data): ... here instead.
	(read_translation_unit_from_input): Do not perform late
	canonicalizing here.  Rather, do it ...
	(read_corpus_from_input): ... here.  Also, call the new
	read_context::clear_per_corpus_data() here.
	(build_function_decl): Do not canonicalize function types here so
	early.  Rather, bind the life time of the function type to the
	life time of the translation unit.
	* src/abg-writer.cc (write_translation_unit): Do not clear the
	type/ID map here.
	* tests/data/test-read-dwarf/test2.so.abi: Adjust test input.

Signed-off-by: Dodji Seketeli <dodji@redhat.com>
This commit is contained in:
Dodji Seketeli 2015-03-17 10:54:12 +01:00
parent 705a6ae186
commit 635e5fa6b2
9 changed files with 260 additions and 68 deletions

View File

@ -464,6 +464,9 @@ void
fqn_to_components(const std::string&,
std::list<string>&);
const shared_ptr<decl_base>
lookup_type_in_corpus(const string&, const corpus&);
const shared_ptr<decl_base>
lookup_type_in_translation_unit(const string&,
const translation_unit&);

View File

@ -179,6 +179,9 @@ class function_decl;
/// Convenience typedef for a shared pointer on a @ref function_type
typedef shared_ptr<function_type> function_type_sptr;
/// Convenience typedef fo a vector of @ref function_type_sptr
typedef vector<function_type_sptr> function_types_type;
/// Convenience typedef for a weak pointer on a @ref function_type
typedef weak_ptr<function_type> function_type_wptr;
@ -258,8 +261,8 @@ public:
bool
operator==(const translation_unit&) const;
function_type_sptr
get_canonical_function_type(function_type_sptr ftype) const;
void
bind_function_type_life_time(function_type_sptr) const;
virtual bool
traverse(ir_node_visitor& v);
@ -578,6 +581,9 @@ class class_decl;
/// Convenience typedef for a shared pointer on a @ref class_decl
typedef shared_ptr<class_decl> class_decl_sptr;
/// Convenience typedef for a vector of @ref class_decl_sptr
typedef vector<class_decl_sptr> classes_type;
/// Convenience typedef for a weak pointer on a @ref class_decl.
typedef weak_ptr<class_decl> class_decl_wptr;

View File

@ -1832,5 +1832,28 @@ corpus::get_exported_decls_builder() const
return priv_->exported_decls_builder;
}
/// Lookup a type definition in all the translation units of a given
/// ABI corpus.
///
/// @param @param qn the fully qualified name of the type to lookup.
///
/// @param abi_corpus the ABI corpus which to look the type up in.
///
/// @return the type definition if any was found, or a NULL pointer.
const decl_base_sptr
lookup_type_in_corpus(const string& qn, const corpus& abi_corpus)
{
decl_base_sptr result;
for (translation_units::const_iterator tu =
abi_corpus.get_translation_units().begin();
tu != abi_corpus.get_translation_units().end();
++tu)
if ((result = lookup_type_in_translation_unit(qn, **tu)))
break;
return result;
}
}// end namespace ir
}// end namespace abigail

View File

@ -108,6 +108,10 @@ typedef stack<scope_decl*> scope_stack_type;
/// value is also a dwarf offset.
typedef unordered_map<Dwarf_Off, Dwarf_Off> offset_offset_map;
/// Convenience typedef for a map which key is a string and which
/// value is a vector of smart pointer to a class.
typedef unordered_map<string, classes_type> string_classes_map;
static bool
find_symbol_table_section(Elf* elf_handle, Elf_Scn*& section);
@ -1729,6 +1733,7 @@ class read_context
die_class_map_type die_wip_classes_map_;
vector<Dwarf_Off> types_to_canonicalize_;
vector<Dwarf_Off> alt_types_to_canonicalize_;
string_classes_map decl_only_classes_map_;
die_tu_map_type die_tu_map_;
corpus_sptr cur_corpus_;
translation_unit_sptr cur_tu_;
@ -1782,6 +1787,17 @@ public:
/// entire ABI corpus.
void
clear_per_translation_unit_data()
{
while (!scope_stack().empty())
scope_stack().pop();
var_decls_to_re_add_to_tree().clear();
type_decl::get_void_type_decl()->set_scope(0);
}
/// Clear the data that is relevant for the current corpus being
/// read.
void
clear_per_corpus_data()
{
die_decl_map().clear();
alternate_die_decl_map().clear();
@ -1789,10 +1805,6 @@ public:
die_type_map(/*in_alt_di=*/false).clear();
types_to_canonicalize(/*in_alt_di=*/true).clear();
types_to_canonicalize(/*in_alt_di=*/false).clear();
while (!scope_stack().empty())
scope_stack().pop();
var_decls_to_re_add_to_tree().clear();
type_decl::get_void_type_decl()->set_scope(0);
}
unsigned short
@ -2170,6 +2182,118 @@ public:
return (i != die_wip_classes_map().end());
}
/// Getter for the map of declaration-only classes that are to be
/// resolved to their definition classes by the end of the corpus
/// loading.
///
/// @return a map of string -> vector of classes where the key is
/// the fully qualified name of the class and the value is the
/// vector of declaration-only class.
const string_classes_map&
declaration_only_classes() const
{return decl_only_classes_map_;}
/// Getter for the map of declaration-only classes that are to be
/// resolved to their definition classes by the end of the corpus
/// loading.
///
/// @return a map of string -> vector of classes where the key is
/// the fully qualified name of the class and the value is the
/// vector of declaration-only class.
string_classes_map&
declaration_only_classes()
{return decl_only_classes_map_;}
/// If a given class is a declaration-only class then stash it on
/// the side so that at the end of the corpus reading we can resolve
/// it to its definition.
///
/// @param klass the class to consider.
void
maybe_schedule_declaration_only_class_for_resolution(class_decl_sptr& klass)
{
if (klass->get_is_declaration_only()
&& klass->get_definition_of_declaration() == 0)
{
string qn = klass->get_qualified_name();
string_classes_map::iterator record =
declaration_only_classes().find(qn);
if (record == declaration_only_classes().end())
declaration_only_classes()[qn].push_back(klass);
else
record->second.push_back(klass);
}
}
/// Test if a given declaration-only class has been scheduled for
/// resolution to a defined class.
///
/// @param klass the class to consider for the test.
///
/// @return true iff @p klass is a declaration-only class and if
/// it's been scheduled for resolution to a defined class.
bool
is_decl_only_class_scheduled_for_resolution(class_decl_sptr& klass)
{
if (klass->get_is_declaration_only())
return (declaration_only_classes().find(klass->get_qualified_name())
!= declaration_only_classes().end());
return false;
}
/// Walk the declaration-only classes that have been found during
/// the building of the corpus and resolve them to their definitions.
void
resolve_declaration_only_classes()
{
vector<string> resolved_classes;
for (string_classes_map::iterator i =
declaration_only_classes().begin();
i != declaration_only_classes().end();
++i)
{
bool to_resolve = false;
for (classes_type::iterator j = i->second.begin();
j != i->second.end();
++j)
if ((*j)->get_is_declaration_only()
&& ((*j)->get_definition_of_declaration() == 0))
to_resolve = true;
if (!to_resolve)
{
resolved_classes.push_back(i->first);
continue;
}
if (decl_base_sptr type_decl = lookup_type_in_corpus(i->first,
*current_corpus()))
{
class_decl_sptr klass = is_class_type(type_decl);
assert(klass);
if (klass->get_is_declaration_only())
klass = klass->get_definition_of_declaration();
assert(!klass->get_is_declaration_only());
for (classes_type::iterator j = i->second.begin();
j != i->second.end();
++j)
{
if ((*j)->get_is_declaration_only()
&& ((*j)->get_definition_of_declaration() == 0))
(*j)->set_definition_of_declaration(klass);
}
resolved_classes.push_back(i->first);
}
}
for (vector<string>::iterator i = resolved_classes.begin();
i != resolved_classes.end();
++i)
declaration_only_classes().erase(*i);
}
/// Return a reference to the vector containing the offsets of the
/// types that need late canonicalizing.
///
@ -2896,6 +3020,31 @@ public:
elf_architecture() const
{return elf_architecture_;}
/// Test if the current elf file being read is an executable.
///
/// @return true iff the current elf file being read is an
/// executable.
bool
current_elf_file_is_executable() const
{
GElf_Ehdr eh_mem;
GElf_Ehdr* elf_header = gelf_getehdr(elf_handle(), &eh_mem);
return elf_header->e_type == ET_EXEC;
}
/// Test if the current elf file being read is a dynamic shared
/// object.
///
/// @return true iff the current elf file being read is a
/// dynamic shared object.
bool
current_elf_file_is_dso() const
{
GElf_Ehdr eh_mem;
GElf_Ehdr* elf_header = gelf_getehdr(elf_handle(), &eh_mem);
return elf_header->e_type == ET_DYN;
}
/// Getter for the map of global variables symbol address -> global
/// variable symbol index.
///
@ -5757,20 +5906,6 @@ build_translation_unit_and_add_to_ir(read_context& ctxt,
result->set_is_constructed(true);
/// Now, look at the types that needs to be canonicalized after the
/// translation has been constructed (which is just now) and
/// canonicalize them.
///
/// The types need to be constructed at the end of the translation
/// unit reading phase because some types are modified by some DIEs
/// even after the principal DIE describing the type has been read;
/// this happens for clones of virtual destructors (for instance) or
/// even for some static data members. We need to that for types
/// are in the alternate debug info section and for types that in
/// the main debug info section.
ctxt.perform_late_type_canonicalizing();
return result;
}
@ -6133,9 +6268,12 @@ build_class_type_and_add_to_ir(read_context& ctxt,
ctxt.associate_die_to_type(dwarf_dieoffset(die), is_in_alt_di, result);
if (!has_child)
{
// TODO: set the access specifier for the declaration-only class
// here.
return result;
ctxt.maybe_schedule_declaration_only_class_for_resolution(result);
return result;
}
ctxt.die_wip_classes_map()[dwarf_dieoffset(die)] = result;
@ -6191,6 +6329,8 @@ build_class_type_and_add_to_ir(read_context& ctxt,
? offset
: -1,
is_virt));
if (b->get_is_declaration_only())
assert(ctxt.is_decl_only_class_scheduled_for_resolution(b));
result->add_base_specifier(base);
}
// Handle data members.
@ -6292,6 +6432,7 @@ build_class_type_and_add_to_ir(read_context& ctxt,
}
}
ctxt.maybe_schedule_declaration_only_class_for_resolution(result);
return result;
}
@ -6952,7 +7093,7 @@ build_function_decl(read_context& ctxt,
tu->get_address_size(),
tu->get_address_size()));
fn_type = tu->get_canonical_function_type(fn_type);
tu->bind_function_type_life_time(fn_type);
result.reset(is_method
? new class_decl::method_decl(fname, fn_type,
@ -6998,6 +7139,8 @@ build_function_decl(read_context& ctxt,
static corpus_sptr
read_debug_info_into_corpus(read_context& ctxt)
{
ctxt.clear_per_corpus_data();
if (!ctxt.current_corpus())
{
corpus_sptr corp (new corpus(ctxt.elf_path()));
@ -7042,6 +7185,22 @@ read_debug_info_into_corpus(read_context& ctxt)
assert(ir_node);
}
ctxt.resolve_declaration_only_classes();
/// Now, look at the types that needs to be canonicalized after the
/// translation has been constructed (which is just now) and
/// canonicalize them.
///
/// The types need to be constructed at the end of the translation
/// unit reading phase because some types are modified by some DIEs
/// even after the principal DIE describing the type has been read;
/// this happens for clones of virtual destructors (for instance) or
/// even for some static data members. We need to that for types
/// are in the alternate debug info section and for types that in
/// the main debug info section.
ctxt.perform_late_type_canonicalizing();
ctxt.current_corpus()->sort_functions();
ctxt.current_corpus()->sort_variables();
@ -7074,7 +7233,13 @@ maybe_canonicalize_type(Dwarf_Off die_offset,
type_base_sptr t = ctxt.lookup_type_from_die_offset(die_offset, in_alt_di);
assert(t);
if (!type_has_non_canonicalized_subtype(t))
if (class_decl_sptr klass = is_class_type(t))
{
if (klass->get_is_declaration_only()
&& (klass->get_definition_of_declaration() == 0))
ctxt.schedule_type_for_late_canonicalization(die_offset, in_alt_di);
}
else if (!type_has_non_canonicalized_subtype(t))
canonicalize(t);
else
ctxt.schedule_type_for_late_canonicalization(die_offset, in_alt_di);

View File

@ -616,7 +616,13 @@ class_decl::hash::operator()(const class_decl& t) const
t.get_base_specifiers().begin();
b != t.get_base_specifiers().end();
++b)
v = hashing::combine_hashes(v, hash_base(**b));
{
class_decl_sptr cl = (*b)->get_base_class();
assert(cl
&& (!cl->get_is_declaration_only()
|| cl->get_definition_of_declaration()));
v = hashing::combine_hashes(v, hash_base(**b));
}
// Hash member types.
#if 0

View File

@ -164,7 +164,7 @@ struct translation_unit::priv
std::string path_;
location_manager loc_mgr_;
mutable global_scope_sptr global_scope_;
mutable fn_type_ptr_map canonical_function_types_;
mutable function_types_type function_types_;
priv()
: is_constructed_(),
@ -301,32 +301,16 @@ translation_unit::operator==(const translation_unit& other)const
return *get_global_scope() == *other.get_global_scope();
}
/// Return the canonical version of a given instance of @ref function_type.
/// Ensure that the life time of a function type is bound to the life
/// time of the current translation unit.
///
/// A given translation units keeps only one copy of each function
/// type that is used in the unit. So each function type that is
/// built somewhere needs to be passed to this function to
/// canonicalize it and/or return the canonical version.
///
/// Note that it really is the translation unit that 'owns' the
/// function types that are live in the translation unit. This is
/// unlike the other kinds of types that are owned by the scope where
/// they are defined.
///
/// @param ftype the function type to canonicalize.
///
/// @return the resulting canonical type.
function_type_sptr
translation_unit::get_canonical_function_type(function_type_sptr ftype) const
{
fn_type_ptr_map::iterator i = priv_->canonical_function_types_.find(ftype);
if (i == priv_->canonical_function_types_.end())
{
priv_->canonical_function_types_[ftype] = true;
return ftype;
}
return dynamic_pointer_cast<function_type>(i->first);
}
/// @param ftype the function time which life time to bind to the life
/// time of the current instance of @ref translation_unit. That is,
/// it's onlyh when the translation unit is destroyed that the
/// function type can be destroyed to.
void
translation_unit::bind_function_type_life_time(function_type_sptr ftype) const
{priv_->function_types_.push_back(ftype);}
/// This implements the ir_traversable_base::traverse virtual
/// function.
@ -3698,6 +3682,10 @@ lookup_node_in_scope(const list<string>& fqn,
node = dynamic_pointer_cast<NodeKind>(*m);
if (node && get_node_name(node) == *c)
{
if (class_decl_sptr cl =
dynamic_pointer_cast<class_decl>(node))
if (cl->get_is_declaration_only())
continue;
resulting_decl = convert_node_to_decl(node);
break;
}

View File

@ -586,11 +586,17 @@ public:
void
clear_per_translation_unit_data()
{
clear_id_xml_node_map();
clear_type_map();
clear_id_xml_node_map();
clear_xml_node_decl_map();
clear_decls_stack();
}
/// Clear all the data that must absolutely be cleared at the end of
/// the parsing of an ABI corpus.
void
clear_per_corpus_data()
{
clear_id_xml_node_map();
clear_type_map();
clear_types_to_canonicalize();
}
@ -962,8 +968,6 @@ read_translation_unit_from_input(read_context& ctxt,
xmlTextReaderNext(reader.get());
ctxt.perform_late_type_canonicalizing();
ctxt.clear_per_translation_unit_data();
return true;
@ -1135,6 +1139,8 @@ read_corpus_from_input(read_context& ctxt)
ctxt.set_corpus(c);
}
ctxt.clear_per_corpus_data();
corpus& corp = *ctxt.get_corpus();
xml::xml_char_sptr path_str = XML_READER_GET_ATTRIBUTE(reader, "path");
@ -1197,6 +1203,7 @@ read_corpus_from_input(read_context& ctxt)
}
while (is_ok);
ctxt.perform_late_type_canonicalizing();
corp.set_origin(corpus::NATIVE_XML_ORIGIN);
return ctxt.get_corpus();;
@ -2075,7 +2082,7 @@ build_function_decl(read_context& ctxt,
if (fn_decl->get_symbol() && fn_decl->get_symbol()->is_public())
fn_decl->set_is_in_public_symbol_table(true);
fn_type = ctxt.get_translation_unit()->get_canonical_function_type(fn_type);
ctxt.get_translation_unit()->bind_function_type_life_time(fn_type);
fn_decl->set_type(fn_type);
ctxt.maybe_canonicalize_type(fn_type);

View File

@ -859,8 +859,6 @@ write_translation_unit(const translation_unit& tu,
ostream& o = ctxt.get_ostream();
const config& c = ctxt.get_config();
ctxt.clear_type_id_map();
do_indent(o, indent);
o << "<abi-instr version='"

View File

@ -48,30 +48,26 @@
<abi-instr version='1.0' address-size='64' path='test2-1.cc'>
<class-decl name='second_type' size-in-bits='64' is-struct='yes' visibility='default' filepath='/home/dodji/git/libabigail/dwarf/tests/data/test-read-dwarf/test2.h' line='12' column='1' id='type-id-7'>
<data-member access='public' layout-offset-in-bits='0'>
<var-decl name='member0' type-id='type-id-8' visibility='default' filepath='/home/dodji/git/libabigail/dwarf/tests/data/test-read-dwarf/test2.h' line='14' column='1'/>
<var-decl name='member0' type-id='type-id-2' visibility='default' filepath='/home/dodji/git/libabigail/dwarf/tests/data/test-read-dwarf/test2.h' line='14' column='1'/>
</data-member>
<data-member access='public' layout-offset-in-bits='32'>
<var-decl name='member1' type-id='type-id-9' visibility='default' filepath='/home/dodji/git/libabigail/dwarf/tests/data/test-read-dwarf/test2.h' line='15' column='1'/>
<var-decl name='member1' type-id='type-id-3' visibility='default' filepath='/home/dodji/git/libabigail/dwarf/tests/data/test-read-dwarf/test2.h' line='15' column='1'/>
</data-member>
<member-function access='public' constructor='yes'>
<function-decl name='second_type' filepath='/home/dodji/git/libabigail/dwarf/tests/data/test-read-dwarf/test2.h' line='17' column='1' visibility='default' binding='global' size-in-bits='64' alignment-in-bits='64'>
<parameter type-id='type-id-10' is-artificial='yes'/>
<parameter type-id='type-id-8' is-artificial='yes'/>
</function-decl>
</member-function>
<member-function access='public' constructor='yes'>
<function-decl name='second_type' mangled-name='_ZN11second_typeC1Ev' filepath='/home/dodji/git/libabigail/dwarf/tests/data/test-read-dwarf/test2.h' line='17' column='1' visibility='default' binding='global' size-in-bits='64' alignment-in-bits='64' elf-symbol-id='_ZN11second_typeC1Ev'>
<parameter type-id='type-id-10' is-artificial='yes'/>
<parameter type-id='type-id-8' is-artificial='yes'/>
</function-decl>
</member-function>
</class-decl>
<type-decl name='int' size-in-bits='32' alignment-in-bits='32' id='type-id-11'/>
<typedef-decl name='integer' type-id='type-id-11' id='type-id-8'/>
<type-decl name='unsigned char' size-in-bits='8' alignment-in-bits='8' id='type-id-12'/>
<typedef-decl name='character' type-id='type-id-12' id='type-id-9'/>
<pointer-type-def type-id='type-id-7' size-in-bits='64' alignment-in-bits='64' id='type-id-10'/>
<pointer-type-def type-id='type-id-7' size-in-bits='64' alignment-in-bits='64' id='type-id-8'/>
<namespace-decl name='a'>
<function-decl name='build_second_type' mangled-name='_ZN1a17build_second_typeEv' filepath='/home/dodji/git/libabigail/dwarf/tests/data/test-read-dwarf/test2-1.cc' line='13' column='1' visibility='default' binding='global' size-in-bits='64' alignment-in-bits='64' elf-symbol-id='_ZN1a17build_second_typeEv'>
<return type-id='type-id-10'/>
<return type-id='type-id-8'/>
</function-decl>
</namespace-decl>
</abi-instr>