SGI Proposal for F-3: Interface Section

Last updated 2 May 2000


Introduction

The purpose of this proposal is to address Issue F-3, consistency checks, along with F-4, empty throw specs, and F-10, function return types.

The proposal is based on a facility that has been in use on SGI/MIPS systems for several years, with the objective of catching mismatches between function calls and definitions. In the form implemented, it is capable of catching result and parameter type mismatches (not precisely in the case of structured types), and specifically of catching problems resulting from passing floating point parameters in floating point registers to varargs functions expecting them in integer registers (with much less overhead).

It has been modified below (in purple) to:

It is worth noting that this facility is quite simple in concept, and its implementation at SGI required little effort. Nevertheless, the description is a bit involved, due to two sources of complexity:


Interface Section

Interface correctness checking by the linkers (ld/ld.so), as well as object file transformations, require information about subprogram interfaces, especially parameter profiles. This section is intended to provide this information. There should be one such section per object file (including executables and DSOs).

These descriptors shall be used to describe both actual subprogram definitions and the parameter profiles of calls. In the latter case, various information will be missing, and once the call is verified to match the profile of a callee, references to its descriptor (e.g. from the events section) may usually be converted to references to the callee's definition descriptor, and the call descriptor may be removed.

The information to be provided has variable length. Thus, the section's contents are organized as a sequence of variable-length descriptors, each with a fixed-length header possibly followed by variable-length data. The descriptors must be sorted by the symbol table index in field symbol. Each descriptor must be a multiple of 8 bytes in size, with null padding between descriptors as required.

Its section attributes are:

name .IA_64.interfaces
sh_type SHT_IA_64_IFACE
sh_link Section header index of the associated symbol table
sh_info 0
sh_flags SHF_SGI_NOSTRIP
requirements Must not be stripped

The structure of an interface descriptor fixed-length header is given by the figure below.


Figure 4-30: Interface Descriptor Header Format
Field Name Type Comments
symbol ElfXX_Word Symbol table index of subprogram name, or 0 for an indirect call
attrs ElfXX_Half Attributes: see Figure 4-31
pcnt ElfXX_Byte Parameter count a, b
fpmask ElfXX_Byte Mask of FP parameter registers c

Notes:

pcnt
a The parameter count and parameter profile below describe the parameter profile as transformed (not as declared). They include implicit parameters inserted by the compiler, including function results converted to implicit result pointers passed as parameters.

b The parameter count includes the result if the SA_FUNCTION attribute is set, and the first descriptor is for the result. If the parameter count is 255, then the parameter list is preceded by a two-byte parameter count. See Figure 4-32.)

fpmask
c This mask indicates which of the eight FP parameter registers are used to pass parameters instead of the corresponding integer registers. The function result is not considered if SA_FUNCTION is set. The lowest-order bits of the mask represent the first parameters, i.e. 0x01 is parameter #1 in f8, 0x02 parameter #2 in f9, etc.


The attributes of a subprogram are encoded in the attrs field as the following bits.


Figure 4-31: Interface Descriptor Subprogram Attributes
Attribute Name Value Comments
SA_PROTOTYPED 0x8000 Does def or ref have prototype?
SA_VARARGS 0x4000 Is this a varargs subprogram?
SA_INSTANTIATION 0x2000 Is this a template instantiation?
SA_SPECIALIZATION 0x1000 Is this a template specialization?
SA_FUNCTION 0x0400 Does subprogram return a result?
SA_NESTED 0x0200 Is subprogram nested?
SA_IGNORE_ERROR 0x0100 Don't enforce consistency.
SA_DEFINITION 0x0080 Is this a definition (not just call)?
SA_THROW_SPEC 0x0040 C++ throw specification follows
SA_FREE_REGS 0x0020 Free register mask follows.
SA_PARAMETERS 0x0010 Parameter profile follows.

Notes:

SA_INSTANTIATION
This function was produced as the result of C++ template instantiation. If it matches another interface descriptor without this flag set, there is a conflict between an instantiation and a specialization.

SA_SPECIALIZATION
This function was produced as the result of C++ template specialization, full or partial. If it matches another interface descriptor without this flag set, there is a conflict between an instantiation and a specialization.

SA_FUNCTION
This is specified as transformed by the compiler, not necessarily as declared, e.g. a subprogram treating a structure result by placing it in a buffer addressed as an implicit first parameter would not be encoded as a function.

SA_NESTED
Because nested subprograms require a static link in addition to the usual declared parameters, if a definition has this attribute all calls should too, but the opposite condition is not necessary.

SA_IGNORE_ERROR
Some subprograms may be known to be called inconsistently. This attribute indicates that any tools checking for inconsistencies should not reject the objects due to inconsistencies for this subprogram.

SA_THROW_SPEC
The parameter profile is followed by a C++ throw specification.

SA_FREE_REGS
A 32-bit free register mask precedes the parameter profile, specifying integer global registers which are never used in this routine. A program transformation tool like pixie may use these registers, subject to the ABI assumptions about caller-saved registers. The mask must be updated by such a tool if registers are used. See Figure 4-32.)

SA_PARAMETERS
Minimal checking may be achieved by including only the fixed-length part of the interface descriptor, and omitting the detailed parameter profile. Doing so changes the meaning of the pcnt field in the fixed-length header. See the description of required linker checking below.


The variable-length part of an interface descriptor consists of a list of parameter descriptors following the fixed-length header, possibly preceded by a profile size, full parameter count, free register mask, and/or throw specification offset and count. It contains one descriptor for each parameter (formal for definitions, actual for calls), plus a leading descriptor for the result of functions. Ellipsis parameters are not present for varargs subprograms; the SA_VARARGS attribute indicates this case. After resolving a reference in a call, the compiler or linker should replace the caller's actual interface reference by a reference to the callee's formal interface if they match. (Observe that a varargs call will never match unless no variable parameters are passed. Therefore, the call descriptor should not be removed unless the callee is defined and non-preemptible, though it may be merged with others that have the same profile.)

The structure of the variable-length part of an interface descriptor is a parameter profile with the form given in the figure below.


Figure 4-32: Interface Descriptor Parameter Profile
Size Description
2 bytes Size in bytes of profile if any other fields are present
2 bytes Parameter count if pcnt == 255, else 0
4 bytes Free register mask if SA_FREE_REGS is set
2 bytes Offset (from size field) of throw spec if SA_THROW_SPEC is set
2 bytes Number of throw spec entries if SA_THROW_SPEC is set
2+ bytes Result type descriptor if SA_FUNCTION is set
2+ bytes Parameter #1 type descriptor
2+ bytes Parameter #2 type descriptor
2+ bytes ...
2+ bytes Parameter #pcnt type descriptor
4 bytes First throw spec identifier (aligned)
4 bytes Second throw spec identifier
...
4 bytes Last throw spec identifier


The structure of each type descriptor is given in the figure below.


Figure 4-33: Interface Descriptor Parameter Type Descriptor
Byte # Description
1 Flags (high-order nibble) and qualifier count (low-order nibble)
2 Fundamental type (see Figure 4-35 below)
3 or 3..6 Size if indeterminate for type,
1 or 4 bytes (not aligned) depending on PDM_SIZE flag
j..j+3 (3,4,7) RTTI symbol index (4 bytes, not aligned) if PDM_RTTI flag is set
k (>=3) First qualifier
k+1 Second qualifier
k+2 Additional qualifiers


Each parameter type descriptor begins with two bytes. It contains several flags and a qualifier count in the first byte as described in Figure 4-34 below, and one of the fundamental types from Figure 4-35 below in the second byte. Next comes the actual parameter length (in bytes) for those types with indeterminate length (e.g. FT_struct, FT_union). If PDM_SIZE is not set, the length is an unsigned byte, where the maximum value (255) implies at least that length; if PDM_SIZE is set it is a 32-bit unsigned word (not necessarily aligned), where the maximum value (0xffffffff) implies at least that length. If PDM_RTTI is set, the next 4-byte unaligned field is a symbol table index of the RTTI descriptor for the type. If the qualifier count is non-zero, it will be followed by that number of single-byte type qualifiers from Figure 4-36 below.

The masks for the initial byte of each type descriptor are given in the figure below.


Figure 4-34: Interface Descriptor Parameter Type Masks
Mask Name Value Comments
PDM_SIZE 0x80 Precise size present?
PDM_REFERENCE 0x40 Reference parameter?
PDM_RTTI 0x20 RTTI symbol for type?
PDM_QUALIFIERS 0x0f Count of type qualifiers >>8


The possible fundamental types for a type descriptor (adapted from, but not identical to, DWARF version 1) are given in the figure below.


Figure 4-35: Fundamental Type Names
Name Value Comments
FT_unknown 0x00 unknown type
FT_signed_char 0x01 8-bit signed character
FT_unsigned_char 0x02 8-bit unsigned character
FT_signed_short 0x03 16-bit signed short integer
FT_unsigned_short 0x04 16-bit unsigned short integer
FT_signed_int32 0x05 32-bit signed integer
FT_unsigned_int32 0x06 32-bit unsigned integer
FT_signed_int64 0x07 64-bit signed integer
FT_unsigned_int64 0x08 64-bit unsigned integer
FT_pointer32 0x09 32-bit pointer
FT_pointer64 0x0a 64-bit pointer
FT_float32 0x0b 32-bit IEEE floating point
FT_float64 0x0c 64-bit IEEE floating point
FT_float128 0x0d 128-bit floating point
FT_complex64 0x0e 64-bit IEEE complex floating point
FT_complex128 0x0f 128-bit IEEE complex floating point
FT_complex256 0x10 256-bit complex floating point
FT_void 0x11 void type
FT_bool32 0x12 32-bit Boolean (TRUE or FALSE)
FT_bool64 0x13 64-bit Boolean (TRUE or FALSE)
FT_label32 0x14 32-bit label (address)
FT_label64 0x15 64-bit label (address)
FT_float80 0x16 80-bit floating point
FT_complex160 0x17 2x80-bit complex floating point
FT_struct 0x20 structure (record)
FT_union 0x21 union (variant)
FT_enum 0x22 enumerated type
FT_class 0x28 C++ class


The fundamental types in Figure 4-35 may be modified by the qualifiers in the figure below, also based on [DWARF-1]:


Figure 4-36: Type Qualifiers
Name Value Comments
MOD_pointer_to 0x01 Pointer to base type
MOD_reference_to 0x02 C++ reference to base type
MOD_const 0x03 const base type
MOD_volatile 0x04 volatile base type
MOD_function 0x05 Function returning base type
MOD_array_of 0x06 Array of base type


If SA_THROW_SPEC is set, the parameter profiles are followed by an array of identifiers for the types that may be thrown by this routine (in C++). The offset field gives the offset from the size field of the first such identifier, at 4-byte alignment (with padding if necessary). The count field gives the number of identifiers present. Each identifier is the 4-byte symbol table index of an RTTI structure for the type. If the function has an empty throw specification, offset and count should both be zero. If SA_THROW_SPEC is not set, nothing may be assumed about the throw specification; it may not be checked.

Optional Fields

To allow minimal overhead checking of varargs floating point parameters, as well as full parameter consistency checking, various parts of the parameter profile, as well as the whole profile, are optional. The following rules clarify which parts may be omitted:

Linker Processing

Full support of the interface descriptor section requires significant processing by the linker -- its purpose is to provide link-time checking. This processing involves checking and compression. This facility has been designed to be used either for minimal checking with minimal space requirements, or for full checking.

Full checking

The compiler may generate a descriptor for every subprogram definition and potentially for every call. (Only one descriptor is required for multiple calls to the same subprogram, and if the callee is in the same compilation, the compiler can check parameters and omit the call descriptors entirely.) When the linker resolves a call to the callee's definition, it should check the call descriptor against the definition descriptor. The rules for compatibility are as follows:

  1. If this is not a varargs routine (not SA_VARARGS), then the number of parameters should match. For each parameter the sizes should match, and whether it is integer or floating point should match.

  2. For a varargs routine (SA_VARARGS), the fixed parameters should match (except perhaps for the last one, which may be a varargs.h va_alist dummy parameter), and there must be no float ing point parameters in the variable part unless the call site had a prototype visible (SA_PROTOTYPED).

Compatibility failures should result in warnings rather than hard errors by default.

Once checking has been done, the linker may (and should) discard most of the descriptors. In general, call descriptors should be discarded if a definition descriptor is available. If there is no definition available, then the linker may verify that the calls are consistent and discard all but one.

Minimal checking

At a minimum, checking should identify cases where floating point parameters have been passed to the variable part of a varargs routine's parameter list; this will not work unless a prototype for the callee was available at the call site. This minimum check requires only the fixed-length part of the descriptors. It requires that the compiler emit descriptors for any varargs routine definitions, and that it emit descriptors for any calls to varargs routines or to routines without prototypes, where floating point actual parameters are passed in registers. These descriptors will not have the SA_PARAMETERS flag set, and their pcnt field should reflect the number of register equivalents (i.e. 64-bit pieces for aggregate parameters) used to pass parameters rather than the number of source-level parameters (plus 1 if SA_FUNCTION is set).

The linker then checks for floating point parameters passed to the variable part of a varargs parameter list using the fpmask information in the fixed-length header and the pcnt field. The first (high-order) pcnt bits of the fpmask field must match. The remaining bits of the caller's fpmask field must be clear. This check is limited to the parameters which may be passed in registers, since those passed in memory do not present varargs matching problems.

Note that, if the callee's descriptor has the SA_PARAMETERS flag set but a minimal test is being done, the actual parameter descriptors must be examined to determine the actual number of parameter registers, rather than the source-level parameter count which pcnt will give.