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_step
  • alloc_base_class_derivation
  • alloc_overriding_virtual_function
  • alloc_base_class
  • alloc_list_entry_for_class
  • alloc_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_type allocates a param-entry pointed to from the routine type supplement of a tk_routine type entry.
  • alloc_template, alloc_template_arg and alloc_template_param_type_descr are used in template processing.
  • alloc_namespace allocates an entry to represent either a namespace or a namespace alias.
  • alloc_using_decl allocates an entry to represent a using-directive, a member using-declaration, or a nonmember using-declaration.
  • alloc_macro allocates an entry representing a macro definition (when RECORD_MACROS_IN_IL is set).
  • alloc_pragma allocates an entry representing a #pragma declaration.
  • alloc_asm_entry allocates an entry representing an asm declaration. To support certain extensions (i.e., when ASM_FUNCTION_ALLOWED is set) alloc_asm_function_body is also available.
  • alloc_scope allocates a scope entry.
  • alloc_ctor_init allocates a constructor-initializer entry, a list of which is recorded in the scope entry associated with a constructor.
  • alloc_temporary_variable allocates a variable entry for a temporary.
  • alloc_dynamic_init allocates a dynamic initialization entry, used (among other things) to represent dynamic initialization on variable declarations.
  • alloc_local_static_variable_init allocates an entry used to represent the initialization of a function-local variable with static storage class.
  • alloc_list_entry_for_routine allocates an entry used to record a list of routine entries (used to support friend declarations).
  • alloc_exception_specification allocates an entry that represents an exception specification in a function declaration.
  • alloc_accessible_base_class allocates an accessible base class entry (unused in implementations in which ABI_CHANGES_FOR_RTTI is set).
  • alloc_hidden_name allocates a hidden-name entry (for use by the C++-generating back end, when RECORD_HIDDEN_NAMES_IN_IL is 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_constant builds an expression node that points to a constant entry.
  • node_for_integer_constant builds an expression node for an integer constant.
  • var_lvalue_expr builds an expression node for a variable as an lvalue.
  • var_rvalue_expr builds an expression node for a variable as an rvalue.
  • var_addr_expr builds an expression node for the address of a variable. The address_taken flag is set.
  • function_lvalue_expr builds an expression node for a function as an lvalue.
  • function_rvalue_node builds 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 the address_taken flag is not set.
  • function_addr_expr builds an expression node for the address of a function. The address_taken flag is set.
  • rvalue_expr_for_lvalue converts an lvalue expression to an rvalue. It also works for function lvalues, but not for arrays.
  • add_indirection_to_node adds an indirection (”*” operator) to an expression.
  • add_ref_indirection_to_node adds the reference equivalent of “*” to an expression.
  • add_address_of_to_node adds a “&” operator to an expression.
  • add_reference_to_to_node adds 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_expr builds an expression for a field selection that produces an lvalue.
  • field_rvalue_selection_expr builds 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 (in symbol_ref.c), which also calls alloc_src_seq_secondary_decl when the declaration is not a primary declaration.
  • For statements it is called by stmt_update_source_sequence_list (in statements.c).
  • In certain other cases – e.g., for the declaration of unnamed enum types, for which there is no symbol – it is called via macros update_source_sequence_list and add_to_source_sequence_list.

Other functions called to create and manage source sequence lists include:

  • alloc_source_sequence_entry allocates and initializes a source-sequence entry.
  • add_source_sequence_entry_to_list is called to add entries to the end of the list of the current scope stack entry (see source_sequence_list in a_scope_stack_entry). When pop_scope is 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 (see source_sequence_list in a_scope).
  • add_empty_source_sequence_entry creates 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_entry creates a source sequence entry to mark the end of a construct for a given entity (see alloc_src_seq_end_of_construct) and adds it to the current source sequence list. (See also Source Sequence Lists.)
  • make_source_sequence_secondary_decl creates a source sequence entry to represent a non-defining declaration (see a_src_seq_secondary_decl), and set_src_seq_secondary_decl_fields can be called to locate the entry on the current source sequence list (see last_matching_source_sequence_entry) and to update various of its pointers and flags.
  • When either CLASS- or NONCLASS_TEMPLATE_INSTANTIATIONS_IN_SOURCE_SEQUENCE_LISTS is TRUE, add_source_sequence_entry_for_partial_instantiation is 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 by insert_instantiation_src_seq_list; in both cases, find_instantiation_insert_scope is 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_list inserts a list into another list at a specified point; it is also called to append a list to another list.
  • remove_from_source_sequence_list removes a given source sequence entry from its list; see also macros unlink_src_seq_entries and unlink_src_seq_entry (which operate on source sequence lists and single entries, respectively).
  • fixup_function_scope_source_sequence_list creates both a sublist header (see alloc_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, and
  • add_to_labels_list
  • add_to_namespaces_list
  • add_to_using_decls_list
  • add_to_asm_entries_list
  • add_to_templates_list
  • add_to_macros_list
  • add_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_name
  • db_type_name
  • db_name_linkage
  • db_type
  • db_abbreviated_type
  • db_access_control
  • db_field
  • db_static_data_member
  • db_member_function
  • db_virtual_function_info
  • db_constant
  • db_variable
  • db_expression
  • db_dynamic_initializer
  • db_initializer
  • db_statement
  • db_statement_list
  • db_object_lifetime
  • db_object_lifetime_stack
  • db_scope
  • db_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_type
  • is_function_type
  • is_incomplete_type
  • is_object_type
  • is_void_type
  • is_integral_type
  • is_signed_integral_type
  • is_enum_typeis_integral_or_enum_type
  • is_character_type
  • is_fixed_point_type
  • is_floating_type
  • is_arithmetic_or_enum_type
  • is_pointer_type
  • is_reference_type
  • is_lvalue_reference_type
  • is_rvalue_reference_type
  • is_ptr_or_ref_type
  • is_scalar_type
  • is_array_type
  • is_char_array_type
  • is_class_struct_union_type
  • is_complete_class_struct_union_type
  • is_union_type
  • is_aggregate_or_union_type
  • is_polymorphic_class_type
  • is_ptr_to_member_type
  • is_template_class_type
  • is_template_param_type
  • is_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_type
  • is_immediate_class_type
  • is_qualified_type
  • is_const_qualified_type
  • is_volatile_qualified_type
  • is_top_level_qualified_type
  • is_top_level_const_qualified_type
  • is_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_type
  • is_or_contains_unnamed_or_local_type
  • is_or_contains_template_param
  • is_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_type
  • is_managed_nullptr_type
  • is_ref_class_type
  • is_value_class_type
  • is_managed_class_type
  • is_standard_class_type
  • is_cli_interface_type
  • is_tracking_reference_type
  • is_handle_or_tracking_ref_type
  • is_interior_ptr_type
  • is_pin_ptr_type
  • is_cli_array_type
  • is_handle_to_cli_array_type
  • is_cli_generic_param_type
  • is_cli_generic_constraint_type
  • is_boxable_type
  • is_delegate_type
  • is_cli_system_object_type
  • is_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_type
  • is_any_ptr_or_ref_type
  • is_pointer_or_handle_type
  • types_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_possible and
  • reinterpret_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).