SGI Proposal for F-3: Interface Section
Last updated 2 May 2000
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 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:
||Section header index of the associated symbol table|
|requirements||Must not be stripped|
The structure of an interface descriptor fixed-length header is given by the figure below.
|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|
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.)
The attributes of a subprogram are encoded in the attrs field as the following bits.
|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.|
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.
|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||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.
|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|
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.
|PDM_SIZE||0x80||Precise size present?|
|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.
|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_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_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|
The fundamental types in Figure 4-35 may be modified by the qualifiers in the figure below, also based on [DWARF-1]:
|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.
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:
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.
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:
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.
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.