8. Table Management#
For a description of the intermediate language itself, see Intermediate Language.
The source file il_alloc.c contains routines to allocate and clear the
intermediate language tables, and il.c contains routines to debug-print
them. These routines should always be used, so that new tables are always set
to the proper default values and problems with undefined fields are avoided.
il_def.h, il_alloc.h, and il.h contain associated declarations.
Note that some of these routines should not be called from a back end, because the front end data structures they need are no longer set up properly at that point. In general, routines that allocate new storage or new entries should not be called, and routines that answer questions about existing entries are okay to call. The
#if !STANDALONE_UTILITY_PROGRAM
guards indicate routines that should not be called by back ends.
8.1. Storage Allocation#
alloc_cil has an interface like malloc; it allocates space in the
current intermediate language memory region. new_il_region begins a new
intermediate language memory region. switch_il_region switches to a
previously-created region. alloc_il allocates space in the file-scope
intermediate language memory region.
When the alternate IL file format is used, space is reserved preceding each IL entry so that the IL entry number can be stored there.
8.2. Constant Entries#
Constant entries (type a_constant) are usually allocated by
alloc_constant; fs_constant is similar to it but allocates the constant
entry at the file scope. make_zero_of_proper_type makes a zero constant of
a given type. clear_constant and set_constant_kind are called to
re-initialize existing constant entries, and set_error_constant changes an
existing constant entry to an error constant.
In most cases, a constant entry need not be unique – the same entry can be
used for all occurrences of a given literal constant. For these cases, one
should call alloc_shareable_constant instead of alloc_constant. (The
interface is slightly different; one passes a filled-in constant entry rather
than just a kind.) alloc_shareable_constant maintains
shareable_constants_table, a hash table that is searched by hashing a
constant entry to a bucket number (routine hash_constant), then searching a
linked list of constants in that bucket to see if there is a match (comparison
is done by eq_constants). If an entry is found, it is reused. If none is
found, one is entered into the table.
In cases where the constant entry cannot be shared (as when it must be linked
into an initializer list), alloc_unshared_constant should be called
instead. It basically just calls alloc_constant, but adjusts a few things
in the constant entry.
8.3. Type Entries#
Type entries (type a_type) are allocated by alloc_type.
integer_type, fixed_point_type, float_type, string_type,
void_type, and error_type can be called to create type entries for the
most common types; these routines will reuse the entries once they have created
them. make_pointer_type creates a pointer type pointing to a given base
type. It uses the routine get_based_type to determine if a pointer to the
type has already been created, and it calls add_based_type_list_member to
record the fact that a pointer type has been allocated.
make_reference_type is the similar routine for (lvalue) reference types,
and make_rvalue_reference_type is the similar routine for rvalue reference
types. ptr_to_member_type is the similar routine for pointer-to-member
types.
make_qualified_type is used to add type qualifiers to an existing type;
make_unqualified_type is used to remove them.
unknown_type creates an unknown type entry; such an entry is used as a
place-holder within the front end and the references to it must be eliminated
before the back end is called. Another type that is normally not seen by the
back end (unless prototype instantiations are recorded in the IL) is the
template parameter type, which is used in processing template declarations.
There are several routines that allocate various pieces of the description of a class type:
alloc_derivation_stepalloc_base_class_derivationalloc_overriding_virtual_functionalloc_base_classalloc_list_entry_for_classalloc_class_type_supplement
8.4. Other Declarative Entries#
alloc_variable, alloc_field, alloc_label, and alloc_routine
allocate variable, field, label, and routine entries. They do nothing terribly
interesting. remove_from_routines_list can be used to remove a routine
from the routines list so that it can be added again at the position where its
actual definition appears.
Routines that allocate other IL entities resulting from declarations include:
alloc_param_typeallocates a param-entry pointed to from the routine type supplement of atk_routinetype entry.alloc_template,alloc_template_argandalloc_template_param_type_descrare used in template processing.alloc_namespaceallocates an entry to represent either a namespace or a namespace alias.alloc_using_declallocates an entry to represent ausing-directive, a memberusing-declaration, or a nonmemberusing-declaration.alloc_macroallocates an entry representing a macro definition (whenRECORD_MACROS_IN_ILis set).alloc_pragmaallocates an entry representing a#pragmadeclaration.alloc_asm_entryallocates an entry representing anasmdeclaration. To support certain extensions (i.e., whenASM_FUNCTION_ALLOWEDis set)alloc_asm_function_bodyis also available.alloc_scopeallocates a scope entry.alloc_ctor_initallocates a constructor-initializer entry, a list of which is recorded in the scope entry associated with a constructor.alloc_temporary_variableallocates a variable entry for a temporary.alloc_dynamic_initallocates a dynamic initialization entry, used (among other things) to represent dynamic initialization on variable declarations.alloc_local_static_variable_initallocates an entry used to represent the initialization of a function-local variable with static storage class.alloc_list_entry_for_routineallocates an entry used to record a list of routine entries (used to supportfrienddeclarations).alloc_exception_specificationallocates an entry that represents an exception specification in a function declaration.alloc_accessible_base_classallocates an accessible base class entry (unused in implementations in whichABI_CHANGES_FOR_RTTIis set).alloc_hidden_nameallocates a hidden-name entry (for use by the C++-generating back end, whenRECORD_HIDDEN_NAMES_IN_ILis set).
8.5. Expressions#
alloc_expr_node allocates an expression node.
set_expr_node_kind and clear_expr_node re-initialize existing entries.
make_operator_node creates an expression node for an operation.
set_node_operator changes the operator or type in an existing operation
expression node.
error_node makes an error expression node (a new one on each call, since
executable constructs cannot be shared).
There are several utility routines that help in constructing expressions:
alloc_node_for_constantbuilds an expression node that points to a constant entry.node_for_integer_constantbuilds an expression node for an integer constant.var_lvalue_exprbuilds an expression node for a variable as an lvalue.var_rvalue_exprbuilds an expression node for a variable as an rvalue.var_addr_exprbuilds an expression node for the address of a variable. Theaddress_takenflag is set.function_lvalue_exprbuilds an expression node for a function as an lvalue.function_rvalue_nodebuilds an expression node for a function as an rvalue, i.e., for the address of the function. This is suitable for calling the function, but not for the “&” operator applied to the function, because theaddress_takenflag is not set.function_addr_exprbuilds an expression node for the address of a function. Theaddress_takenflag is set.rvalue_expr_for_lvalueconverts an lvalue expression to an rvalue. It also works for function lvalues, but not for arrays.add_indirection_to_nodeadds an indirection (”*” operator) to an expression.add_ref_indirection_to_nodeadds the reference equivalent of “*” to an expression.add_address_of_to_nodeadds a “&” operator to an expression.add_reference_to_to_nodeadds the reference equivalent of “&” to an expression. (Note that that is not a typo; the name really does have “to_to_” in it.)field_lvalue_selection_exprbuilds an expression for a field selection that produces an lvalue.field_rvalue_selection_exprbuilds an expression for a field selection that produces an rvalue.
copy_expr_tree makes a copy of an expression tree. When it does so, it can
optionally “clone” any temporary variables found within the tree so that the
copy uses different but equivalent temporaries.
In addition, alloc_lowered_eh_construct_node allocates an expression node
to represent one of several (partially) lowered exception handling constructs
(used only when IL lowering is enabled and DO_FULL_PORTABLE_EH_LOWERING is
configured to FALSE.
8.6. Statements#
alloc_statement allocates a statement (which may allocate a supplement of
type a_for_loop, a_switch_stmt_descr, etc.).
alloc_switch_case_entry allocates the entry used to represent one case
(possibly, the “default:” case) of a switch statement.
8.7. Object Lifetimes#
alloc_object_lifetime allocates an object lifetime entry, and
bind_object_lifetime binds it to a specified IL entry (a scope, an
expression, a dynamic init, etc.).
A dynamic init entry with a destructor is added to the destructions list of an
object lifetime by record_end_of_lifetime_destruction, which calls
add_to_destructions_list. Conversely, it is removed from the list by
remove_from_destructions_list.
During front-end processing object lifetime entries are pushed onto the object
lifetime stack (see push_object_lifetime and global variable
curr_object_lifetime, which is the top of the stack) and popped off the
stack when appropriate (see pop_object_lifetime).
If, when it is popped off the object lifetime stack, the entry is not actually
required in the IL (see is_useless_object_lifetime), it is unbound from the
IL entry with which it is associated (see unbind_object_lifetime) and
placed on an available list for reuse (see free_object_lifetime).
8.8. Source Sequence Entries#
Source-sequence entries are created only when
GENERATE_SOURCE_SOURCE_SEQUENCE_LISTS is configured to TRUE. See
Intermediate Language for additional information.
f_update_source_sequence_list creates new source sequence entries and adds
them to the appropriate list.
- For declarations it is called by
sym_update_source_sequence_list(insymbol_ref.c), which also callsalloc_src_seq_secondary_declwhen the declaration is not a primary declaration. - For statements it is called by
stmt_update_source_sequence_list(instatements.c). - In certain other cases – e.g., for the declaration of unnamed
enumtypes, for which there is no symbol – it is called via macrosupdate_source_sequence_listandadd_to_source_sequence_list.
Other functions called to create and manage source sequence lists include:
alloc_source_sequence_entryallocates and initializes a source-sequence entry.add_source_sequence_entry_to_listis called to add entries to the end of the list of the current scope stack entry (seesource_sequence_listina_scope_stack_entry). Whenpop_scopeis called, the list for a given scope stack entry is either appended to the list of the enclosing scope stack entry or, for function scopes and the file scope, moved to the associated IL scope (seesource_sequence_listina_scope).add_empty_source_sequence_entrycreates and adds a source sequence entry that does not yet point to an IL entry. Such entries are placeholders to reserve a position in the list for a declarator when the particular IL entry it represents is not yet known; they do not persist beyond the front end.add_end_of_construct_source_sequence_entrycreates a source sequence entry to mark the end of a construct for a given entity (seealloc_src_seq_end_of_construct)and adds it to the current source sequence list. (See also Source Sequence Lists.)make_source_sequence_secondary_declcreates a source sequence entry to represent a non-defining declaration (seea_src_seq_secondary_decl), andset_src_seq_secondary_decl_fieldscan be called to locate the entry on the current source sequence list (seelast_matching_source_sequence_entry) and to update various of its pointers and flags.- When either
CLASS- orNONCLASS_TEMPLATE_INSTANTIATIONS_IN_SOURCE_SEQUENCE_LISTSis TRUE,add_source_sequence_entry_for_partial_instantiationis called to enter a secondary declaration entry to represent the declaration of a compiler-generated template specialization that stands for a template instantiation, and when the body of such a specialization is generated, its source sequence list is added byinsert_instantiation_src_seq_list; in both cases,find_instantiation_insert_scopeis called to locate the point at which the instantiation should be inserted in the source sequence list (since it is not necessarily at the end of the list for the current scope stack entry). insert_src_seq_listinserts a list into another list at a specified point; it is also called to append a list to another list.remove_from_source_sequence_listremoves a given source sequence entry from its list; see also macrosunlink_src_seq_entriesandunlink_src_seq_entry(which operate on source sequence lists and single entries, respectively).fixup_function_scope_source_sequence_listcreates both a sublist header (seealloc_src_seq_sublist) and the source sequence entry that points to it.
8.9. Lists of Entries#
The routines
add_to_constants_list,add_to_types_list,add_to_variables_list,add_to_parameters_list,add_to_dynamic_inits_list,add_to_routines_list, andadd_to_labels_listadd_to_namespaces_listadd_to_using_decls_listadd_to_asm_entries_listadd_to_templates_listadd_to_macros_listadd_to_pragma_list
add entries of the indicated type to end of the list of similar entries under a
scope entry. The scope will vary depending on the type of entry and what the
caller specifies. In many cases it is either the current scope or the
innermost namespace scope that is used (e.g., variables, constants, types).
Routines that are not class members are added to the list for the file scope or
a namespace scope. Labels are always added to an sck_function scope. And
so forth.
There is also an add_to_scopes_list; it is called by create_block_scope
when a scope for a statement block is created. Special handling is required
because the IL scope for a block is not created until there is some declaration
in that block.
8.10. Orphaned Entries#
Orphans are entries in the file scope memory region that are referenced only from function scope memory regions. When the parents of such entries are written out and then removed from memory, the entries are orphaned because they are not attached to the rest of the file-scope IL tree.
The orphan mechanism makes lists of such entries so that they can be found
during traversal of the file-scope IL. In the file scope, each entry is
preceded by space for an orphan-list pointer. When a potential orphan entry is
encountered, for example when an entry in a function scope memory region points
to an entry in the file scope memory region,
add_orphaned_file_scope_il_entry is called. It adds the potential orphan
to a list of entries of its type headed by the proper element of the array
orphaned_file_scope_il_entries and linked by the hidden pointer. An entry
is ignored if it is already on the list (which can be discerned from the fact
that the orphan-list pointer is already non-NULL or the fact that the entry
address matches the last-on-list pointer for that entry type).
There is also a special mechanism for keeping track of the lists of local
static variables and types in function and block scopes. These are lists that
are entirely in the file scope memory region but originate with a pointer that
is in a function scope memory region. To keep track of these, entries of type
a_scope_orphaned_list_header are allocated in the file scope memory region
and attached to the il_header. Each entry contains a copy of the static
variables and types pointers from a function or block scope, so that the lists
can be visited in the file scope memory region even if the associated function
scope memory region is no longer in memory. See
add_scope_orphaned_il_lists.
8.11. Source File Information#
record_start_of_source_file and record_end_of_source_file build
information about the correspondence between source files and sequence numbers.
Basically, this is a tree that shows the nesting of include files, with added
entries to show the information provided by #line directives. These
routines are called from push_input_stack and pop_input_stack in
lexical.c, and proc_line in preproc.c.
conv_seq_to_file_and_line uses that data structure to map sequence numbers
into file names and line numbers.
8.12. Debug Print Routines#
The following debug-print routines can be used to display intermediate language constructs for debugging purposes:
db_namedb_type_namedb_name_linkagedb_typedb_abbreviated_typedb_access_controldb_fielddb_static_data_memberdb_member_functiondb_virtual_function_infodb_constantdb_variabledb_expressiondb_dynamic_initializerdb_initializerdb_statementdb_statement_listdb_object_lifetimedb_object_lifetime_stackdb_scopedb_source_sequence_entry
8.12.1. Types#
The routines and data structures described in this section are defined in
types.c and types.h. These are routines that manipulate intermediate
language types in various ways. They answer questions about them, and do
various language transformations on them.
8.13. Typerefs#
In type trees, typedefs, type qualifiers (e.g., const and volatile), type
operators such as decltype, and various other types are represented by an entry
called a typeref. It indicates a reference to an existing type. For qualified
pointer types, the typeref indicating the qualifier is on top of the
pointer type: for int *const q the type is a consttyperef
pointing to a pointer type entry, which points to an entry for int. Macros
get_type_qualifiers and get_top_level_type_qualifiers (the former may
check for qualifiers on the underlying array element type, the latter does not)
call f_get_type_qualifiers when appropriate, returning a bit set (see
a_type_qualifier_set).
It is often necessary for code that deals with types to access the target of a typeref or chain of typerefs. Various convenience functions are provided for this purpose, including skip_typerefs, skip_typerefs_not_typedefs, skip_lexical_typerefs, etc.
8.14. Asking Questions About Types#
There are many predicates that determine whether or not a given type is of a
given kind. Having predicate functions (instead of doing the testing by direct
coding) is necessary because of the possibility of there being typeref
entries on a type. These predicates are implemented as functions, although
they could be implemented as macros for any cases where it seems desirable.
Within types.c, macros are used so as to avoid having one predicate routine
call a half-dozen others to get an answer. The predicate macros have the same
names as the corresponding predicate functions, with the final _type of the
name removed (i.e., is_integral corresponds to is_integral_type). They
do not call skip_typerefs.
The predicate functions are
is_error_typeis_function_typeis_incomplete_typeis_object_typeis_void_typeis_integral_typeis_signed_integral_typeis_enum_typeis_integral_or_enum_typeis_character_typeis_fixed_point_typeis_floating_typeis_arithmetic_or_enum_typeis_pointer_typeis_reference_typeis_lvalue_reference_typeis_rvalue_reference_typeis_ptr_or_ref_typeis_scalar_typeis_array_typeis_char_array_typeis_class_struct_union_typeis_complete_class_struct_union_typeis_union_typeis_aggregate_or_union_typeis_polymorphic_class_typeis_ptr_to_member_typeis_template_class_typeis_template_param_typeis_nullptr_type
Macros that may be called to get information about the type (when
skip_typerefs is not to be called) include
is_immediate_error_typeis_immediate_class_typeis_qualified_typeis_const_qualified_typeis_volatile_qualified_typeis_top_level_qualified_typeis_top_level_const_qualified_typeis_top_level_volatile_qualified_type
(Among those that check type qualifiers, the is_top_level_xxx versions
differ from the others in not checking for qualifiers on underlying array
element types.) Other type qualifiers besides const and volatile may be
recognized; for instance, when support for restrict is enabled,
is_restrict_qualified_type and is_top_level_restrict_qualified_type are
also defined.
is_illegal_abstract_class_type returns TRUE if the type is that of an
abstract class, struct, or union, or if it is an array of abstract class
objects, or if it is pointer or reference to an array of abstract class
objects.
int_kind_is_signed determines whether a given integer kind is a signed
type.
There are in addition some predicate functions that examine an entire type tree, if necessary, to return information about a type:
is_or_contains_local_typeis_or_contains_unnamed_or_local_typeis_or_contains_template_paramis_or_contains_specific_template_param
These routines call traverse_type_tree, a generic type-walking routine; it
is passed the address of a service routine that examines a node in the type
tree, and its detailed behavior is governed by setting some combination of
flags (see a_type_tree_traversal_flag_set).
Another set of predicates tests C++/CLI types and can be used only inside
#if guards for MICROSOFT_EXTENSIONS_ALLOWED:
is_handle_typeis_managed_nullptr_typeis_ref_class_typeis_value_class_typeis_managed_class_typeis_standard_class_typeis_cli_interface_typeis_tracking_reference_typeis_handle_or_tracking_ref_typeis_interior_ptr_typeis_pin_ptr_typeis_cli_array_typeis_handle_to_cli_array_typeis_cli_generic_param_typeis_cli_generic_constraint_typeis_boxable_typeis_delegate_typeis_cli_system_object_typeis_cli_system_string_type
To ease pointer/reference/handle tests in code that might have to deal with C++/CLI types, the following test certain general categories of pointers and references:
is_any_reference_typeis_any_ptr_or_ref_typeis_pointer_or_handle_typetypes_are_both_pointers_or_both_handles
8.15. Taking Apart Types#
Given an array type, array_element_type and
underlying_array_element_type return the type of an element; the second
deals with multidimensional arrays. type_pointed_to is used to get the
base type from a pointer or reference type. pm_member_type and
pm_class_type extract the base types from pointer-to-member types.
underlying_type_of_derived_type extracts the underlying type for all
derived types (pointer, pointer-to-member, array, etc.).
Given a C++/CLI array type, cli_array_element_type returns the type of the
element. cli_array_rank_constant and cli_array_rank return the rank
(number of dimensions) of the array in different forms.
8.16. Setting Sizes and Alignments of Types#
The size and alignment for a type are recorded in the type entry (except for
typerefs, which have their sizes and alignments dictated by the type they
refer to). These values, however, are not set all over the front end, but only
by isolated routines. The main routine is set_type_size, which will set
the size of an arbitrary type. The type must be completely built by the time
this routine is called. set_type_size calls set_array_type_size for
arrays. Size and alignment for class types are set by code in layout.c.
8.17. Modifying Type Trees#
set_used_in_exception_or_rtti_flag is called for types that appear as the
type of a throw object, in an exception specification list, as the type of a
handler, or in a typeid operator. It sets the type entry’s
used_in_exception_or_rtti flag. To assure that such types have external
linkage, it also calls set_force_external_linkage_flag, which in turn calls
traverse_type_tree to mark all the components of the type tree that have
linkage.
strip_local_typedefs is called to remove all local typedef components
of a type tree. It calls traverse_and_modify_type_tree to help accomplish
this task. If a subtree of the type tree is modified, a new type tree is built
to accommodate the change.
8.18. Getting Information About Base Classes#
find_base_class_of returns a pointer to a base class entry if a given class
type is represented on the base classes list of another class type. Similar
functionality, but with the implied restrictions, is provided by
find_direct_base_class_of and find_direct_or_virtual_base_class_of.
related_class_pointers will do the same for pointers to classes.
corresponding_base_class, given a base class belonging to one class,
searches the base classes of another class, finds the corresponding entry, and
returns a pointer to it.
is_on_any_derivation_of returns TRUE if a given base class is on the
derivation of another base class.
8.19. Routines that Implement Language Concepts#
The routines described in this section implement concepts of the C or C++ languages.
Two routines handle type promotions: type_after_integral_promotion
determines the type that results from integral promotion done in an expression,
and default_argument_promotion determines the type of an argument to a
function with old-style parameter declarations after default promotion. See
also type_after_bit_field_integral_promotion in exprutil.c for a
special case involving bit-fields.
Several routines compare two types to see if they are compatible in some way.
identical_types determines whether the two types are structurally identical
(they don’t have to be the same pointer). il_identical_types sees if the
two types are identical from an IL perspective (e.g., int and long
might be identical if the front end is configured that way).
types_are_compatible sees if two types are compatible (C standard,
3.1.2.6). (Those three names are actually macros which call
f_identical_types and f_types_are_compatible only if the type pointers
are not exactly the same.) interchangeable_types sees if two types are
interchangeable as arguments to functions (basically, this is true if they are
compatible except for signedness issues).
impl_pointer_conversion determines whether or not an implicit conversion
from a given type to a given pointer type is valid, with some special cases for
pointers to void and for null pointer constants.
impl_conversion_possible determines whether or not an implicit conversion
from a given type to another is valid. impl_ptr_to_member_conversion
handles the subcase of implicit conversion of one pointer-to-member type to
another.
expl_conversion_possible determines whether or not an explicit conversion
(cast) from a given type to another is valid. It relies on two subroutines for
the major subcases:
static_cast_conversion_possibleandreinterpret_cast_conversion_possible.
composite_type develops a composite type from two types (C standard,
3.1.2.6).
overload_distinguishable determines whether or not two function types are
sufficiently different that they can be distinguished by overload processing
(and therefore routines with those types can be overloaded).
impl_handle_conversion determines whether or not an implicit conversion
from a given type to a given C++/CLI handle type is valid.
boxing_conversion_possible determines whether a conversion from one given
type to another is a C++/CLI boxing conversion. The cases accepted convert a
value type to a handle to that value type.
unboxing_conversion_possible determines whether a conversion from one given
type to another is a C++/CLI unboxing conversion. The cases accepted convert a
handle type to a value type.
system_type_from_fundamental_type and fundamental_type_from_system_type
are a pair of functions that convert back and forth between fundamental types
and the corresponding C++/CLI value class types (e.g., between int and
System::Int32).