
This section describes various of the implementation details of the
C++ producer TDF output. In particular it describes the standard
TDF tokens used to represent the target dependent aspects of the language
and to provide links into the run-time system. Many of these tokens
are common to the C and C++ producers. Those which are unique to
the C++ producer have names of the form ~cpp.*. Note
that the description is in terms of TDF tokens, not the internal tokens
introduced by the
#pragma token syntax.
There are two levels of implementation in the run-time system. The actual interface between the producer and the run-time system is given by the standard tokens. The provided implementation defines these tokens in a way appropriate to itself. An alternative implementation would have to define the tokens differently. It is intended that the standard tokens are sufficiently generic to allow a variety of implementations to hook into the producer output in the manner they require.
The representations of the basic arithmetic types are target dependent,
so, for example, an int may contain 16, 32, 64 or some
other number of bits. Thus it is necessary to introduce a token to
stand for each of the built-in arithmetic types (including the
long long types).
Each integral type is represented by a VARIETY token
as follows:
| Type | Token | Encoding |
|---|---|---|
| char | ~char | 0 |
| signed char | ~signed_char | 0 | 4 = 4 |
| unsigned char | ~unsigned_char | 0 | 8 = 8 |
| signed short | ~signed_short | 1 | 4 = 5 |
| unsigned short | ~unsigned_short | 1 | 8 = 9 |
| signed int | ~signed_int | 2 | 4 = 6 |
| unsigned int | ~unsigned_int | 2 | 8 = 10 |
| signed long | ~signed_long | 3 | 4 = 7 |
| unsigned long | ~unsigned_long | 3 | 8 = 11 |
| signed long long | ~signed_longlong | 3 | 4 | 16 = 23 |
| unsigned long long | ~unsigned_longlong | 3 | 8 | 16 = 27 |
Similarly each floating point type is represent by a
FLOATING_VARIETY token:
| Type | Token |
|---|---|
| float | ~float |
| double | ~double |
| long double | ~long_double |
Each integral type also has an encoding as a SIGNED_NAT
as shown above. This number is a bit pattern built up from the following
values:
| Type | Encoding |
|---|---|
| char | 0 |
| short | 1 |
| int | 2 |
| long | 3 |
| signed | 4 |
| unsigned | 8 |
| long long | 16 |
Any target dependent integral type can be represented by a
SIGNED_NAT token using this encoding. This representation,
rather than one based on VARIETYs, is used for ease of
manipulation. The token:
~convert : ( SIGNED_NAT ) -> VARIETYgives the mapping from the integral encoding to the representing variety. For example, it will map
6 to ~signed_int.
The token:
~promote : ( SIGNED_NAT ) -> SIGNED_NATdescribes how to form the promotion of an integral type according to the ISO C/C++ value preserving rules, and is used by the producer to represent target dependent promotion types. For example, the promotion of
unsigned short may be int or unsigned
int depending on the representation of these types; that is
to say, ~promote ( 9 ) will be 6 on some
machines and 10 on others. Although ~promote
is used by default, a program may specify another token with the same
sort signature to be used in its place by means of the directive:
#pragma TenDRA compute promote identifierFor example, a standard token
~sign_promote is defined
which gives the older C sign preserving promotion rules. In addition,
the promotion of an individual type can be specified using:
#pragma TenDRA promoted type-id : promoted-type-id
The token:
~arith_type : ( SIGNED_NAT, SIGNED_NAT ) -> SIGNED_NATsimilarly describes how to form the usual arithmetic result type from two promoted integral operand types. For example, the arithmetic type of
long and unsigned int may be
long or unsigned long depending on the representation
of these types; that is to say,
~arith_type ( 7, 10 ) will be 7 on some
machines and 11 on others.
Any tokenised type declared using:
#pragma token VARIETY v # tvwill be represented by a
SIGNED_NAT token with external
name
tv corresponding to the encoding of v.
Special cases of this are the implementation dependent integral types
which arise naturally within the language. The external token names
for these types are given below:
| Type | Token |
|---|---|
| bool | ~cpp.bool |
| ptrdiff_t | ptrdiff_t |
| size_t | size_t |
| wchar_t | wchar_t |
So, for example, a sizeof expression has shape
~convert ( size_t ). The token ~cpp.bool
is defined in the default implementation, but the other tokens are
defined according to their definitions on the target machine in the
normal API library building mechanism.
The type of an integer literal is defined
in terms of the first in a list of possible integral types. The first
type in which the literal value can be represented gives the type
of the literal. For small literals it is possible to work out the
type exactly, however for larger literals the result is target dependent.
For example, the literal 50000 will have type int
on machines in which 50000 fits into an int,
and
long otherwise. This target dependent mapping is given
by a series of tokens of the form:
~lit_* : ( SIGNED_NAT ) -> SIGNED_NATwhich map a literal value to the representation of an integral type. The token used depends on the list of possible types, which in turn depends on the base used to represent the literal and the integer suffix used, as given in the following table:
| Base | Suffix | Token | Types |
|---|---|---|---|
| decimal | none | ~lit_int | int, long, unsigned long |
| octal | none | ~lit_hex | int, unsigned int, long, unsigned long |
| hexadecimal | none | ~lit_hex | int, unsigned int, long, unsigned long |
| any | U | ~lit_unsigned | unsigned int, unsigned long |
| any | L | ~lit_long | long, unsigned long |
| any | UL | ~lit_ulong | unsigned long |
| any | LL | ~lit_longlong | long long, unsigned long long |
| any | ULL | ~lit_ulonglong | unsigned long long |
Thus, for example, the shape of the integer literal 50000 is:
~convert ( ~lit_int ( 50000 ) )
The sign of a plain bitfield type, declared without using
signed or unsigned, is left unspecified
in C and C++. The token:
~cpp.bitf_sign : ( SIGNED_NAT ) -> BOOLis used to give a mapping from integral types to the sign of a plain bitfield of that type, in a form suitable for use in the TDF
bfvar_bits construct. (Note that ~cpp.bitf_sign
should have been a standard C token but was omitted.)
TDF has no concept of a generic pointer type, so tokens are used to
defer the representation of void * and the basic operations
on it to the target machine. The fundamental token is:
~ptr_void : () -> SHAPEwhich gives the representation of
void *. This shape
will be denoted by pv in the description of the following
tokens. It is not guaranteed that pv is a TDF pointer
shape, although normally it will be implemented as a pointer to a
suitable alignment.
The token:
~null_pv : () -> EXP pvgives the value of a null pointer of type
void *. Generic
pointers can also be converted to and from other pointers. These
conversions are represented by the tokens:
~to_ptr_void : ( ALIGNMENT a, EXP POINTER a ) -> EXP pv ~from_ptr_void : ( ALIGNMENT a, EXP pv ) -> EXP POINTER awhere the given alignment describes the destination or source pointer type. Finally a generic pointer may be tested against the null pointer or two generic pointers may be compared. These operations are represented by the tokens:
~pv_test : ( EXP pv, LABEL, NTEST ) -> EXP TOP ~cpp.pv_compare : ( EXP pv, EXP pv, LABEL, NTEST ) -> EXP TOPwhere the given
NTEST gives the comparison to be applied
and the given label gives the destination to jump to if the test fails.
(Note that ~cpp.pv_compare should have been a standard
C token but was omitted.)
Several conversions in C and C++ can only be represented by undefined TDF. For example, converting a pointer to an integer can only be represented in TDF by forming a union of the pointer and integer shapes, putting the pointer into the union and pulling the integer out. Such conversions are tokenised. Undefined conversions not mentioned below may be performed by combining those given with the standard, well-defined, conversions.
The token:
~ptr_to_ptr : ( ALIGNMENT a, ALIGNMENT b, EXP POINTER a ) -> EXP POINTER bis used to convert between two incompatible pointer types. The first alignment describes the source pointer shape while the second describes the destination pointer shape. Note that if the destination alignment is greater than the source alignment then the source pointer can be used in most TDF constructs in place of the destination pointer, so the use of
~ptr_to_ptr can be omitted (the exception
is
pointer_test which requires equal alignments). Base
class pointer conversions are examples of these well-behaved, alignment
preserving conversions.
The tokens:
~f_to_pv : ( EXP PROC ) -> EXP pv ~pv_to_f : ( EXP pv ) -> EXP PROCare used to convert pointers to functions to and from
void *
(these conversions are not allowed in ISO C/C++ but are in older dialects).
The tokens:
~i_to_p : ( VARIETY v, ALIGNMENT a, EXP INTEGER v ) -> EXP POINTER a ~p_to_i : ( ALIGNMENT a, VARIETY v, EXP POINTER a ) -> EXP INTEGER v ~i_to_pv : ( VARIETY v, EXP INTEGER v ) -> EXP pv ~pv_to_i : ( VARIETY v, EXP pv ) -> EXP INTEGER vare used to convert integers to and from
void * and other
pointers.
The precise form of the integer division and remainder operations in C and C++ is left unspecified with respect to the sign of the result if either operand is negative. The tokens:
~div : ( EXP INTEGER v, EXP INTEGER v ) -> EXP INTEGER v ~rem : ( EXP INTEGER v, EXP INTEGER v ) -> EXP INTEGER vare used to represent integer division and remainder. They will map onto one of the pairs of TDF constructs,
div0 and rem0,
div1 and rem1 or div2 and
rem2.
The function calling conventions used by the C++ producer are essentially the same as those used by the C producer with one exception. That is to say, all types except arrays are passed by value (note that individual installers may modify these conventions to conform to their own ABIs).
The exception concerns classes with a non-trivial constructor, destructor or assignment operator. These classes are passed as function arguments by taking a reference to a copy of the object (although it is often possible to eliminate the copy and pass a reference to the object directly). They are passed as function return values by adding an extra parameter to the start of the function parameters giving a reference to a location into which the return value should be copied.
Non-static member functions are implemented in the obvious fashion, by passing a pointer to the object the method is being applied to as the first argument (or the second argument if the method has an extra argument for its return value).
Calls to functions declared with ellipses are via the
apply_proc TDF construct, with all the arguments being
treated as non-variable. However the definition of such a function
uses the make_proc construct with a variable parameter.
This parameter can be referred to within the program using the
... expression. The
type of this expression is given by the built-in token:
~__va_t : () -> SHAPEThe
va_start macro declared in the
<stdarg.h> header then describes how the variable
parameter (expressed as ...) can be converted to an expression
of type va_list suitable for use in the
va_arg macro.
Note that the variable parameter is in effect only being used to determine where the first optional parameter is defined. The assumption is that all such parameters are located contiguously on the stack, however the fact that calls to such functions do not use the variable parameter mechanism means that this is not automatically the case. Strictly speaking this means that the implementation of ellipsis functions uses undefined behaviour in TDF, however given the non-type-safe function calling rules in C this is unavoidable and installers need to make provision for such calls (by dumping any parameters from registers to the stack if necessary). Given the theoretically type-safe nature of C++ it would be possible to avoid such undefined behaviour, but the need for C-compatible calling conventions prevents this.
The representation of, and operations on, pointers to data members are represented by tokens to allow for a variety of implementations. It is assumed that all pointers to data members (as opposed to pointers to function members) are represented by the same shape:
~cpp.pm.type : () -> SHAPEThis shape will be denoted by
pm in the description of
the following tokens.
There are two basic methods of constructing a pointer to a data member.
The first is to take the address of a data member of a class. A data
member is represented in TDF by an expression which gives the offset
of the member from the start of its enclosing compound
shape (note that it is not possible to take the address of a member
of a virtual base). The mapping from this offset to a pointer to a
data member is given by:
~cpp.pm.make : ( EXP OFFSET ) -> EXP pmThe second way of constructing a pointer to a data member is to use a null pointer to member:
~cpp.pm.null : () -> EXP pmThe other fundamental operation on a pointer to data member is to turn it back into an offset expression which can be added to a pointer to a class to access a member of that class in a
.* or
->*
operation. This is done by the token:
~cpp.pm.offset : ( EXP pm, ALIGNMENT a ) -> EXP OFFSET ( a, a )Note that it is necessary to specify an alignment in order to describe the shape of the result. The value of this token is undefined if the given expression is a null pointer to data member.
A pointer to a data member of a non-virtual base class can be converted
to a pointer to a data member of a derived class. The reverse conversion
is also possible using static_cast. If the base is a
primary base class then these conversions are
trivial and have no effect. Otherwise null pointers to data members
are converted to null pointers to data members, and the non-null cases
are handled by the tokens:
~cpp.pm.cast : ( EXP pm, EXP OFFSET ) -> EXP pm ~cpp.pm.uncast : ( EXP pm, EXP OFFSET ) -> EXP pmwhere the given offset is the offset of the base class within the derived class. It is also possible to convert between any two pointers to data members using
reinterpret_cast. This conversion
is implied by the equality of representation between any two pointers
to data members and has no effect.
The only remaining operations on pointer to data members are to test one against the null pointer to data member and to compare two pointer to data members. These are represented by the tokens:
~cpp.pm.test : ( EXP pm, LABEL, NTEST ) -> EXP TOP ~cpp.pm.compare : ( EXP pm, EXP pm, LABEL, NTEST ) -> EXP TOPwhere the given
NTEST gives the comparison to be applied
and the given label gives the destination to jump to if the test fails.
In the default implementation, pointers to data members are implemented
as int. The null pointer to member is represented by
0 and the address of a class member is represented by 1 plus the offset
of the member (in bytes). Casting to and from a derived class then
correspond to adding or subtracting the base class offset (in bytes),
and pointer to member comparisons correspond to integer comparisons.
As with pointers to data members, pointers to function members and the operations on them are represented by tokens to allow for a range of implementations. All pointers to function members are represented by the same shape:
~cpp.pmf.type : () -> SHAPEThis shape will be denoted by
pmf in the description
of the following tokens. Many of the tokens take an expression which
has a shape which is a pointer to the alignment of pmf.
This will be denoted by ppmf.
There are two basic methods for constructing a pointer to a function member. The first is to take the address of a non-static member function of a class. There are two cases, depending on whether or not the member function is virtual. The non-virtual case is given by the token:
~cpp.pmf.make : ( EXP PROC, EXP OFFSET, EXP OFFSET ) -> EXP pmfwhere the first argument is the address of the corresponding function, the second argument gives any base class offset which is to be added when calling this function (to deal with inherited member functions), and the third argument is a zero offset.
For virtual functions, a pointer to function member of the form above is entered in the virtual function table for the corresponding class. The actual pointer to the virtual function member then gives a reference into the virtual function table as follows:
~cpp.pmf.vmake : ( SIGNED_NAT, EXP OFFSET, EXP, EXP ) -> EXP pmfwhere the first argument gives the index of the function within the virtual function table, the second argument gives the offset of the vptr field within the class, and the third and fourth arguments are zero offsets.
The second way of constructing a pointer to a function member is to use a null pointer to function member:
~cpp.pmf.null : () -> EXP pmf ~cpp.pmf.null2 : () -> EXP pmfFor technical reasons there are two versions of this token, although they have the same value. The first token is used in static initialisers; the second token is used in other expressions.
The cast operations on pointers to function members are more complex than those on pointers to data members. The value to be cast is copied into a temporary and one of the tokens:
~cpp.pmf.cast : ( EXP ppmf, EXP OFFSET, EXP, EXP OFFSET ) -> EXP TOP ~cpp.pmf.uncast : ( EXP ppmf, EXP OFFSET, EXP, EXP OFFSET ) -> EXP TOPis applied to modify the value of the temporary according to the given cast. The first argument gives the address of the temporary, the second gives the base class offset to be added or subtracted, the third gives the number to be added or subtracted to convert virtual function indexes for the base class into virtual function indexes for the derived class, and the fourth gives the offset of the vptr field within the class. Again, the ability to use
reinterpret_cast
to convert between any two pointer to function member types arises
because of the uniform representation of these types.
As with pointers to data members, there are tokens implementing comparisons on pointers to function members:
~cpp.pmf.test : ( EXP ppmf, LABEL, NTEST ) -> EXP TOP ~cpp.pmf.compare : ( EXP ppmf, EXP ppmf, LABEL, NTEST ) -> EXP TOPNote however that the arguments are passed by reference.
The most important, and most complex, operation is calling a function through a pointer to function member. The first step is to copy the pointer to function member into a temporary. The token:
~cpp.pmf.virt : ( EXP ppmf, EXP, ALIGNMENT ) -> EXP TOPis then applied to the temporary to convert a pointer to a virtual function member to a normal pointer to function member by looking it up in the corresponding virtual function table. The first argument gives the address of the temporary, the second gives the object to which the function is to be applied, and the third gives the alignment of the corresponding class. Now the base class conversion to be applied to the object can be determined by applying the token:
~cpp.pmf.delta : ( ALIGNMENT a, EXP ppmf ) -> EXP OFFSET ( a, a )to the temporary to find the offset to be added. Finally the function to be called can be extracted from the temporary using the token:
~cpp.pmf.func : ( EXP ppmf ) -> EXP PROCThe function call then procedes as normal.
The default implementation is that described in the ARM, where each pointer to function member is represented in the form:
struct PTR_MEM_FUNC {
short delta ;
short index ;
union {
void ( *func ) () ;
short off ;
} u ;
} ;
The delta field gives the base class offset (in bytes)
to be added before applying the function. The index
field is 0 for null pointers, -1 for non-virtual function pointers
and the index into the virtual function table for virtual function
pointers (as described below these indexes start from 1). For non-virtual
function pointers the function itself is given by the u.func
field. For virtual function pointers the offset of the vptr
field within the class is given by the u.off field.
Consider a class with no base classes:
class A {
// A's members
} ;
Each object of class A needs its own copy of the non-static
data members of A and, for polymorphic types, a means of referencing
the virtual function table and run-time type information for A.
This is accomplished using a layout of the form:
TDF has no concept of a generic pointer type, so tokens are used to
defer the representation of void * and the basic operations
on it to the target machine. The fundamental token is:
~ptr_void : () -> SHAPEwhich gives the representation of
void *. This shape
will be denoted by pv in the description of the following
tokens. It is not guaranteed that pv is a TDF pointer
shape, although normally it will be implemented as a pointer to a
suitable alignment.
The token:
~null_pv : () -> EXP pvgives the value of a null pointer of type
void *. Generic
pointers can also be converted to and from other pointers. These
conversions are represented by the tokens:
~to_ptr_void : ( ALIGNMENT a, EXP POINTER a ) -> EXP pv ~from_ptr_void : ( ALIGNMENT a, EXP pv ) -> EXP POINTER awhere the given alignment describes the destination or source pointer type. Finally a generic pointer may be tested against the null pointer or two generic pointers may be compared. These operations are represented by the tokens:
~pv_test : ( EXP pv, LABEL, NTEST ) -> EXP TOP ~cpp.pv_compare : ( EXP pv, EXP pv, LABEL, NTEST ) -> EXP TOPwhere the given
NTEST gives the comparison to be applied
and the given label gives the destination to jump to if the test fails.
(Note that ~cpp.pv_compare should have been a standard
C token but was omitted.)
Several conversions in C and C++ can only be represented by undefined TDF. For example, converting a pointer to an integer can only be represented in TDF by forming a union of the pointer and integer shapes, putting the pointer into the union and pulling the integer out. Such conversions are tokenised. Undefined conversions not mentioned below may be performed by combining those given with the standard, well-defined, conversions.
The token:
~ptr_to_ptr : ( ALIGNMENT a, ALIGNMENT b, EXP POINTER a ) -> EXP POINTER bis used to convert between two incompatible pointer types. The first alignment describes the source pointer shape while the second describes the destination pointer shape. Note that if the destination alignment is greater than the source alignment then the source pointer can be used in most TDF constructs in place of the destination pointer, so the use of
~ptr_to_ptr can be omitted (the exception
is
pointer_test which requires equal alignments). Base
class pointer conversions are examples of these well-behaved, alignment
preserving conversions.
The tokens:
~f_to_pv : ( EXP PROC ) -> EXP pv ~pv_to_f : ( EXP pv ) -> EXP PROCare used to convert pointers to functions to and from
void *
(these conversions are not allowed in ISO C/C++ but are in older dialects).
The tokens:
~i_to_p : ( VARIETY v, ALIGNMENT a, EXP INTEGER v ) -> EXP POINTER a ~p_to_i : ( ALIGNMENT a, VARIETY v, EXP POINTER a ) -> EXP INTEGER v ~i_to_pv : ( VARIETY v, EXP INTEGER v ) -> EXP pv ~pv_to_i : ( VARIETY v, EXP pv ) -> EXP INTEGER vare used to convert integers to and from
void * and other
pointers.
The precise form of the integer division and remainder operations in C and C++ is left unspecified with respect to the sign of the result if either operand is negative. The tokens:
~div : ( EXP INTEGER v, EXP INTEGER v ) -> EXP INTEGER v ~rem : ( EXP INTEGER v, EXP INTEGER v ) -> EXP INTEGER vare used to represent integer division and remainder. They will map onto one of the pairs of TDF constructs,
div0 and rem0,
div1 and rem1 or div2 and
rem2.
The function calling conventions used by the C++ producer are essentially the same as those used by the C producer with one exception. That is to say, all types except arrays are passed by value (note that individual installers may modify these conventions to conform to their own ABIs).
The exception concerns classes with a non-trivial constructor, destructor or assignment operator. These classes are passed as function arguments by taking a reference to a copy of the object (although it is often possible to eliminate the copy and pass a reference to the object directly). They are passed as function return values by adding an extra parameter to the start of the function parameters giving a reference to a location into which the return value should be copied.
Non-static member functions are implemented in the obvious fashion, by passing a pointer to the object the method is being applied to as the first argument (or the second argument if the method has an extra argument for its return value).
Calls to functions declared with ellipses are via the
apply_proc TDF construct, with all the arguments being
treated as non-variable. However the definition of such a function
uses the make_proc construct with a variable parameter.
This parameter can be referred to within the program using the
... expression. The
type of this expression is given by the built-in token:
~__va_t : () -> SHAPEThe
va_start macro declared in the
<stdarg.h> header then describes how the variable
parameter (expressed as ...) can be converted to an expression
of type va_list suitable for use in the
va_arg macro.
Note that the variable parameter is in effect only being used to determine where the first optional parameter is defined. The assumption is that all such parameters are located contiguously on the stack, however the fact that calls to such functions do not use the variable parameter mechanism means that this is not automatically the case. Strictly speaking this means that the implementation of ellipsis functions uses undefined behaviour in TDF, however given the non-type-safe function calling rules in C this is unavoidable and installers need to make provision for such calls (by dumping any parameters from registers to the stack if necessary). Given the theoretically type-safe nature of C++ it would be possible to avoid such undefined behaviour, but the need for C-compatible calling conventions prevents this.
The representation of, and operations on, pointers to data members are represented by tokens to allow for a variety of implementations. It is assumed that all pointers to data members (as opposed to pointers to function members) are represented by the same shape:
~cpp.pm.type : () -> SHAPEThis shape will be denoted by
pm in the description of
the following tokens.
There are two basic methods of constructing a pointer to a data member.
The first is to take the address of a data member of a class. A data
member is represented in TDF by an expression which gives the offset
of the member from the start of its enclosing compound
shape (note that it is not possible to take the address of a member
of a virtual base). The mapping from this offset to a pointer to a
data member is given by:
~cpp.pm.make : ( EXP OFFSET ) -> EXP pmThe second way of constructing a pointer to a data member is to use a null pointer to member:
~cpp.pm.null : () -> EXP pmThe other fundamental operation on a pointer to data member is to turn it back into an offset expression which can be added to a pointer to a class to access a member of that class in a
.* or
->*
operation. This is done by the token:
~cpp.pm.offset : ( EXP pm, ALIGNMENT a ) -> EXP OFFSET ( a, a )Note that it is necessary to specify an alignment in order to describe the shape of the result. The value of this token is undefined if the given expression is a null pointer to data member.
A pointer to a data member of a non-virtual base class can be converted
to a pointer to a data member of a derived class. The reverse conversion
is also possible using static_cast. If the base is a
primary base class then these conversions are
trivial and have no effect. Otherwise null pointers to data members
are converted to null pointers to data members, and the non-null cases
are handled by the tokens:
~cpp.pm.cast : ( EXP pm, EXP OFFSET ) -> EXP pm ~cpp.pm.uncast : ( EXP pm, EXP OFFSET ) -> EXP pmwhere the given offset is the offset of the base class within the derived class. It is also possible to convert between any two pointers to data members using
reinterpret_cast. This conversion
is implied by the equality of representation between any two pointers
to data members and has no effect.
The only remaining operations on pointer to data members are to test one against the null pointer to data member and to compare two pointer to data members. These are represented by the tokens:
~cpp.pm.test : ( EXP pm, LABEL, NTEST ) -> EXP TOP ~cpp.pm.compare : ( EXP pm, EXP pm, LABEL, NTEST ) -> EXP TOPwhere the given
NTEST gives the comparison to be applied
and the given label gives the destination to jump to if the test fails.
In the default implementation, pointers to data members are implemented
as int. The null pointer to member is represented by
0 and the address of a class member is represented by 1 plus the offset
of the member (in bytes). Casting to and from a derived class then
correspond to adding or subtracting the base class offset (in bytes),
and pointer to member comparisons correspond to integer comparisons.
As with pointers to data members, pointers to function members and the operations on them are represented by tokens to allow for a range of implementations. All pointers to function members are represented by the same shape:
~cpp.pmf.type : () -> SHAPEThis shape will be denoted by
pmf in the description
of the following tokens. Many of the tokens take an expression which
has a shape which is a pointer to the alignment of pmf.
This will be denoted by ppmf.
There are two basic methods for constructing a pointer to a function member. The first is to take the address of a non-static member function of a class. There are two cases, depending on whether or not the member function is virtual. The non-virtual case is given by the token:
~cpp.pmf.make : ( EXP PROC, EXP OFFSET, EXP OFFSET ) -> EXP pmfwhere the first argument is the address of the corresponding function, the second argument gives any base class offset which is to be added when calling this function (to deal with inherited member functions), and the third argument is a zero offset.
For virtual functions, a pointer to function member of the form above is entered in the virtual function table for the corresponding class. The actual pointer to the virtual function member then gives a reference into the virtual function table as follows:
~cpp.pmf.vmake : ( SIGNED_NAT, EXP OFFSET, EXP, EXP ) -> EXP pmfwhere the first argument gives the index of the function within the virtual function table, the second argument gives the offset of the vptr field within the class, and the third and fourth arguments are zero offsets.
The second way of constructing a pointer to a function member is to use a null pointer to function member:
~cpp.pmf.null : () -> EXP pmf ~cpp.pmf.null2 : () -> EXP pmfFor technical reasons there are two versions of this token, although they have the same value. The first token is used in static initialisers; the second token is used in other expressions.
The cast operations on pointers to function members are more complex than those on pointers to data members. The value to be cast is copied into a temporary and one of the tokens:
~cpp.pmf.cast : ( EXP ppmf, EXP OFFSET, EXP, EXP OFFSET ) -> EXP TOP ~cpp.pmf.uncast : ( EXP ppmf, EXP OFFSET, EXP, EXP OFFSET ) -> EXP TOPis applied to modify the value of the temporary according to the given cast. The first argument gives the address of the temporary, the second gives the base class offset to be added or subtracted, the third gives the number to be added or subtracted to convert virtual function indexes for the base class into virtual function indexes for the derived class, and the fourth gives the offset of the vptr field within the class. Again, the ability to use
reinterpret_cast
to convert between any two pointer to function member types arises
because of the uniform representation of these types.
As with pointers to data members, there are tokens implementing comparisons on pointers to function members:
~cpp.pmf.test : ( EXP ppmf, LABEL, NTEST ) -> EXP TOP ~cpp.pmf.compare : ( EXP ppmf, EXP ppmf, LABEL, NTEST ) -> EXP TOPNote however that the arguments are passed by reference.
The most important, and most complex, operation is calling a function through a pointer to function member. The first step is to copy the pointer to function member into a temporary. The token:
~cpp.pmf.virt : ( EXP ppmf, EXP, ALIGNMENT ) -> EXP TOPis then applied to the temporary to convert a pointer to a virtual function member to a normal pointer to function member by looking it up in the corresponding virtual function table. The first argument gives the address of the temporary, the second gives the object to which the function is to be applied, and the third gives the alignment of the corresponding class. Now the base class conversion to be applied to the object can be determined by applying the token:
~cpp.pmf.delta : ( ALIGNMENT a, EXP ppmf ) -> EXP OFFSET ( a, a )to the temporary to find the offset to be added. Finally the function to be called can be extracted from the temporary using the token:
~cpp.pmf.func : ( EXP ppmf ) -> EXP PROCThe function call then procedes as normal.
The default implementation is that described in the ARM, where each pointer to function member is represented in the form:
struct PTR_MEM_FUNC {
short delta ;
short index ;
union {
void ( *func ) () ;
short off ;
} u ;
} ;
The delta field gives the base class offset (in bytes)
to be added before applying the function. The index
field is 0 for null pointers, -1 for non-virtual function pointers
and the index into the virtual function table for virtual function
pointers (as described below these indexes start from 1). For non-virtual
function pointers the function itself is given by the u.func
field. For virtual function pointers the offset of the vptr
field within the class is given by the u.off field.
Consider a class with no base classes:
class A {
// A's members
} ;
Each object of class A needs its own copy of the non-static
data members of A and, for polymorphic types, a means of referencing
the virtual function table and run-time type information for A.
This is accomplished using a layout of the form:
TDF has no concept of a generic pointer type, so tokens are used to
defer the representation of void * and the basic operations
on it to the target machine. The fundamental token is:
~ptr_void : () -> SHAPEwhich gives the representation of
void *. This shape
will be denoted by pv in the description of the following
tokens. It is not guaranteed that pv is a TDF pointer
shape, although normally it will be implemented as a pointer to a
suitable alignment.
The token:
~null_pv : () -> EXP pvgives the value of a null pointer of type
void *. Generic
pointers can also be converted to and from other pointers. These
conversions are represented by the tokens:
~to_ptr_void : ( ALIGNMENT a, EXP POINTER a ) -> EXP pv ~from_ptr_void : ( ALIGNMENT a, EXP pv ) -> EXP POINTER awhere the given alignment describes the destination or source pointer type. Finally a generic pointer may be tested against the null pointer or two generic pointers may be compared. These operations are represented by the tokens:
~pv_test : ( EXP pv, LABEL, NTEST ) -> EXP TOP ~cpp.pv_compare : ( EXP pv, EXP pv, LABEL, NTEST ) -> EXP TOPwhere the given
NTEST gives the comparison to be applied
and the given label gives the destination to jump to if the test fails.
(Note that ~cpp.pv_compare should have been a standard
C token but was omitted.)
Several conversions in C and C++ can only be represented by undefined TDF. For example, converting a pointer to an integer can only be represented in TDF by forming a union of the pointer and integer shapes, putting the pointer into the union and pulling the integer out. Such conversions are tokenised. Undefined conversions not mentioned below may be performed by combining those given with the standard, well-defined, conversions.
The token:
~ptr_to_ptr : ( ALIGNMENT a, ALIGNMENT b, EXP POINTER a ) -> EXP POINTER bis used to convert between two incompatible pointer types. The first alignment describes the source pointer shape while the second describes the destination pointer shape. Note that if the destination alignment is greater than the source alignment then the source pointer can be used in most TDF constructs in place of the destination pointer, so the use of
~ptr_to_ptr can be omitted (the exception
is
pointer_test which requires equal alignments). Base
class pointer conversions are examples of these well-behaved, alignment
preserving conversions.
The tokens:
~f_to_pv : ( EXP PROC ) -> EXP pv ~pv_to_f : ( EXP pv ) -> EXP PROCare used to convert pointers to functions to and from
void *
(these conversions are not allowed in ISO C/C++ but are in older dialects).
The tokens:
~i_to_p : ( VARIETY v, ALIGNMENT a, EXP INTEGER v ) -> EXP POINTER a ~p_to_i : ( ALIGNMENT a, VARIETY v, EXP POINTER a ) -> EXP INTEGER v ~i_to_pv : ( VARIETY v, EXP INTEGER v ) -> EXP pv ~pv_to_i : ( VARIETY v, EXP pv ) -> EXP INTEGER vare used to convert integers to and from
void * and other
pointers.
The precise form of the integer division and remainder operations in C and C++ is left unspecified with respect to the sign of the result if either operand is negative. The tokens:
~div : ( EXP INTEGER v, EXP INTEGER v ) -> EXP INTEGER v ~rem : ( EXP INTEGER v, EXP INTEGER v ) -> EXP INTEGER vare used to represent integer division and remainder. They will map onto one of the pairs of TDF constructs,
div0 and rem0,
div1 and rem1 or div2 and
rem2.
The function calling conventions used by the C++ producer are essentially the same as those used by the C producer with one exception. That is to say, all types except arrays are passed by value (note that individual installers may modify these conventions to conform to their own ABIs).
The exception concerns classes with a non-trivial constructor, destructor or assignment operator. These classes are passed as function arguments by taking a reference to a copy of the object (although it is often possible to eliminate the copy and pass a reference to the object directly). They are passed as function return values by adding an extra parameter to the start of the function parameters giving a reference to a location into which the return value should be copied.
Non-static member functions are implemented in the obvious fashion, by passing a pointer to the object the method is being applied to as the first argument (or the second argument if the method has an extra argument for its return value).
Calls to functions declared with ellipses are via the
apply_proc TDF construct, with all the arguments being
treated as non-variable. However the definition of such a function
uses the make_proc construct with a variable parameter.
This parameter can be referred to within the program using the
... expression. The
type of this expression is given by the built-in token:
~__va_t : () -> SHAPEThe
va_start macro declared in the
<stdarg.h> header then describes how the variable
parameter (expressed as ...) can be converted to an expression
of type va_list suitable for use in the
va_arg macro.
Note that the variable parameter is in effect only being used to determine where the first optional parameter is defined. The assumption is that all such parameters are located contiguously on the stack, however the fact that calls to such functions do not use the variable parameter mechanism means that this is not automatically the case. Strictly speaking this means that the implementation of ellipsis functions uses undefined behaviour in TDF, however given the non-type-safe function calling rules in C this is unavoidable and installers need to make provision for such calls (by dumping any parameters from registers to the stack if necessary). Given the theoretically type-safe nature of C++ it would be possible to avoid such undefined behaviour, but the need for C-compatible calling conventions prevents this.
The representation of, and operations on, pointers to data members are represented by tokens to allow for a variety of implementations. It is assumed that all pointers to data members (as opposed to pointers to function members) are represented by the same shape:
~cpp.pm.type : () -> SHAPEThis shape will be denoted by
pm in the description of
the following tokens.
There are two basic methods of constructing a pointer to a data member.
The first is to take the address of a data member of a class. A data
member is represented in TDF by an expression which gives the offset
of the member from the start of its enclosing compound
shape (note that it is not possible to take the address of a member
of a virtual base). The mapping from this offset to a pointer to a
data member is given by:
~cpp.pm.make : ( EXP OFFSET ) -> EXP pmThe second way of constructing a pointer to a data member is to use a null pointer to member:
~cpp.pm.null : () -> EXP pmThe other fundamental operation on a pointer to data member is to turn it back into an offset expression which can be added to a pointer to a class to access a member of that class in a
.* or
->*
operation. This is done by the token:
~cpp.pm.offset : ( EXP pm, ALIGNMENT a ) -> EXP OFFSET ( a, a )Note that it is necessary to specify an alignment in order to describe the shape of the result. The value of this token is undefined if the given expression is a null pointer to data member.
A pointer to a data member of a non-virtual base class can be converted
to a pointer to a data member of a derived class. The reverse conversion
is also possible using static_cast. If the base is a
primary base class then these conversions are
trivial and have no effect. Otherwise null pointers to data members
are converted to null pointers to data members, and the non-null cases
are handled by the tokens:
~cpp.pm.cast : ( EXP pm, EXP OFFSET ) -> EXP pm ~cpp.pm.uncast : ( EXP pm, EXP OFFSET ) -> EXP pmwhere the given offset is the offset of the base class within the derived class. It is also possible to convert between any two pointers to data members using
reinterpret_cast. This conversion
is implied by the equality of representation between any two pointers
to data members and has no effect.
The only remaining operations on pointer to data members are to test one against the null pointer to data member and to compare two pointer to data members. These are represented by the tokens:
~cpp.pm.test : ( EXP pm, LABEL, NTEST ) -> EXP TOP ~cpp.pm.compare : ( EXP pm, EXP pm, LABEL, NTEST ) -> EXP TOPwhere the given
NTEST gives the comparison to be applied
and the given label gives the destination to jump to if the test fails.
In the default implementation, pointers to data members are implemented
as int. The null pointer to member is represented by
0 and the address of a class member is represented by 1 plus the offset
of the member (in bytes). Casting to and from a derived class then
correspond to adding or subtracting the base class offset (in bytes),
and pointer to member comparisons correspond to integer comparisons.
As with pointers to data members, pointers to function members and the operations on them are represented by tokens to allow for a range of implementations. All pointers to function members are represented by the same shape:
~cpp.pmf.type : () -> SHAPEThis shape will be denoted by
pmf in the description
of the following tokens. Many of the tokens take an expression which
has a shape which is a pointer to the alignment of pmf.
This will be denoted by ppmf.
There are two basic methods for constructing a pointer to a function member. The first is to take the address of a non-static member function of a class. There are two cases, depending on whether or not the member function is virtual. The non-virtual case is given by the token:
~cpp.pmf.make : ( EXP PROC, EXP OFFSET, EXP OFFSET ) -> EXP pmfwhere the first argument is the address of the corresponding function, the second argument gives any base class offset which is to be added when calling this function (to deal with inherited member functions), and the third argument is a zero offset.
For virtual functions, a pointer to function member of the form above is entered in the virtual function table for the corresponding class. The actual pointer to the virtual function member then gives a reference into the virtual function table as follows:
~cpp.pmf.vmake : ( SIGNED_NAT, EXP OFFSET, EXP, EXP ) -> EXP pmfwhere the first argument gives the index of the function within the virtual function table, the second argument gives the offset of the vptr field within the class, and the third and fourth arguments are zero offsets.
The second way of constructing a pointer to a function member is to use a null pointer to function member:
~cpp.pmf.null : () -> EXP pmf ~cpp.pmf.null2 : () -> EXP pmfFor technical reasons there are two versions of this token, although they have the same value. The first token is used in static initialisers; the second token is used in other expressions.
The cast operations on pointers to function members are more complex than those on pointers to data members. The value to be cast is copied into a temporary and one of the tokens:
~cpp.pmf.cast : ( EXP ppmf, EXP OFFSET, EXP, EXP OFFSET ) -> EXP TOP ~cpp.pmf.uncast : ( EXP ppmf, EXP OFFSET, EXP, EXP OFFSET ) -> EXP TOPis applied to modify the value of the temporary according to the given cast. The first argument gives the address of the temporary, the second gives the base class offset to be added or subtracted, the third gives the number to be added or subtracted to convert virtual function indexes for the base class into virtual function indexes for the derived class, and the fourth gives the offset of the vptr field within the class. Again, the ability to use
reinterpret_cast
to convert between any two pointer to function member types arises
because of the uniform representation of these types.
As with pointers to data members, there are tokens implementing comparisons on pointers to function members:
~cpp.pmf.test : ( EXP ppmf, LABEL, NTEST ) -> EXP TOP ~cpp.pmf.compare : ( EXP ppmf, EXP ppmf, LABEL, NTEST ) -> EXP TOPNote however that the arguments are passed by reference.
The most important, and most complex, operation is calling a function through a pointer to function member. The first step is to copy the pointer to function member into a temporary. The token:
~cpp.pmf.virt : ( EXP ppmf, EXP, ALIGNMENT ) -> EXP TOPis then applied to the temporary to convert a pointer to a virtual function member to a normal pointer to function member by looking it up in the corresponding virtual function table. The first argument gives the address of the temporary, the second gives the object to which the function is to be applied, and the third gives the alignment of the corresponding class. Now the base class conversion to be applied to the object can be determined by applying the token:
~cpp.pmf.delta : ( ALIGNMENT a, EXP ppmf ) -> EXP OFFSET ( a, a )to the temporary to find the offset to be added. Finally the function to be called can be extracted from the temporary using the token:
~cpp.pmf.func : ( EXP ppmf ) -> EXP PROCThe function call then procedes as normal.
The default implementation is that described in the ARM, where each pointer to function member is represented in the form:
struct PTR_MEM_FUNC {
short delta ;
short index ;
union {
void ( *func ) () ;
short off ;
} u ;
} ;
The delta field gives the base class offset (in bytes)
to be added before applying the function. The index
field is 0 for null pointers, -1 for non-virtual function pointers
and the index into the virtual function table for virtual function
pointers (as described below these indexes start from 1). For non-virtual
function pointers the function itself is given by the u.func
field. For virtual function pointers the offset of the vptr
field within the class is given by the u.off field.
Consider a class with no base classes:
class A {
// A's members
} ;
Each object of class A needs its own copy of the non-static
data members of A and, for polymorphic types, a means of referencing
the virtual function table and run-time type information for A.
This is accomplished using a layout of the form:
TDF has no concept of a generic pointer type, so tokens are used to
defer the representation of void * and the basic operations
on it to the target machine. The fundamental token is:
~ptr_void : () -> SHAPEwhich gives the representation of
void *. This shape
will be denoted by pv in the description of the following
tokens. It is not guaranteed that pv is a TDF pointer
shape, although normally it will be implemented as a pointer to a
suitable alignment.
The token:
~null_pv : () -> EXP pvgives the value of a null pointer of type
void *. Generic
pointers can also be converted to and from other pointers. These
conversions are represented by the tokens:
~to_ptr_void : ( ALIGNMENT a, EXP POINTER a ) -> EXP pv ~from_ptr_void : ( ALIGNMENT a, EXP pv ) -> EXP POINTER awhere the given alignment describes the destination or source pointer type. Finally a generic pointer may be tested against the null pointer or two generic pointers may be compared. These operations are represented by the tokens:
~pv_test : ( EXP pv, LABEL, NTEST ) -> EXP TOP ~cpp.pv_compare : ( EXP pv, EXP pv, LABEL, NTEST ) -> EXP TOPwhere the given
NTEST gives the comparison to be applied
and the given label gives the destination to jump to if the test fails.
(Note that ~cpp.pv_compare should have been a standard
C token but was omitted.)
Several conversions in C and C++ can only be represented by undefined TDF. For example, converting a pointer to an integer can only be represented in TDF by forming a union of the pointer and integer shapes, putting the pointer into the union and pulling the integer out. Such conversions are tokenised. Undefined conversions not mentioned below may be performed by combining those given with the standard, well-defined, conversions.
The token:
~ptr_to_ptr : ( ALIGNMENT a, ALIGNMENT b, EXP POINTER a ) -> EXP POINTER bis used to convert between two incompatible pointer types. The first alignment describes the source pointer shape while the second describes the destination pointer shape. Note that if the destination alignment is greater than the source alignment then the source pointer can be used in most TDF constructs in place of the destination pointer, so the use of
~ptr_to_ptr can be omitted (the exception
is
pointer_test which requires equal alignments). Base
class pointer conversions are examples of these well-behaved, alignment
preserving conversions.
The tokens:
~f_to_pv : ( EXP PROC ) -> EXP pv ~pv_to_f : ( EXP pv ) -> EXP PROCare used to convert pointers to functions to and from
void *
(these conversions are not allowed in ISO C/C++ but are in older dialects).
The tokens:
~i_to_p : ( VARIETY v, ALIGNMENT a, EXP INTEGER v ) -> EXP POINTER a ~p_to_i : ( ALIGNMENT a, VARIETY v, EXP POINTER a ) -> EXP INTEGER v ~i_to_pv : ( VARIETY v, EXP INTEGER v ) -> EXP pv ~pv_to_i : ( VARIETY v, EXP pv ) -> EXP INTEGER vare used to convert integers to and from
void * and other
pointers.
The precise form of the integer division and remainder operations in C and C++ is left unspecified with respect to the sign of the result if either operand is negative. The tokens:
~div : ( EXP INTEGER v, EXP INTEGER v ) -> EXP INTEGER v ~rem : ( EXP INTEGER v, EXP INTEGER v ) -> EXP INTEGER vare used to represent integer division and remainder. They will map onto one of the pairs of TDF constructs,
div0 and rem0,
div1 and rem1 or div2 and
rem2.
The function calling conventions used by the C++ producer are essentially the same as those used by the C producer with one exception. That is to say, all types except arrays are passed by value (note that individual installers may modify these conventions to conform to their own ABIs).
The exception concerns classes with a non-trivial constructor, destructor or assignment operator. These classes are passed as function arguments by taking a reference to a copy of the object (although it is often possible to eliminate the copy and pass a reference to the object directly). They are passed as function return values by adding an extra parameter to the start of the function parameters giving a reference to a location into which the return value should be copied.
Non-static member functions are implemented in the obvious fashion, by passing a pointer to the object the method is being applied to as the first argument (or the second argument if the method has an extra argument for its return value).
Calls to functions declared with ellipses are via the
apply_proc TDF construct, with all the arguments being
treated as non-variable. However the definition of such a function
uses the make_proc construct with a variable parameter.
This parameter can be referred to within the program using the
... expression. The
type of this expression is given by the built-in token:
~__va_t : () -> SHAPEThe
va_start macro declared in the
<stdarg.h> header then describes how the variable
parameter (expressed as ...) can be converted to an expression
of type va_list suitable for use in the
va_arg macro.
Note that the variable parameter is in effect only being used to determine where the first optional parameter is defined. The assumption is that all such parameters are located contiguously on the stack, however the fact that calls to such functions do not use the variable parameter mechanism means that this is not automatically the case. Strictly speaking this means that the implementation of ellipsis functions uses undefined behaviour in TDF, however given the non-type-safe function calling rules in C this is unavoidable and installers need to make provision for such calls (by dumping any parameters from registers to the stack if necessary). Given the theoretically type-safe nature of C++ it would be possible to avoid such undefined behaviour, but the need for C-compatible calling conventions prevents this.
The representation of, and operations on, pointers to data members are represented by tokens to allow for a variety of implementations. It is assumed that all pointers to data members (as opposed to pointers to function members) are represented by the same shape:
~cpp.pm.type : () -> SHAPEThis shape will be denoted by
pm in the description of
the following tokens.
There are two basic methods of constructing a pointer to a data member.
The first is to take the address of a data member of a class. A data
member is represented in TDF by an expression which gives the offset
of the member from the start of its enclosing compound
shape (note that it is not possible to take the address of a member
of a virtual base). The mapping from this offset to a pointer to a
data member is given by:
~cpp.pm.make : ( EXP OFFSET ) -> EXP pmThe second way of constructing a pointer to a data member is to use a null pointer to member:
~cpp.pm.null : () -> EXP pmThe other fundamental operation on a pointer to data member is to turn it back into an offset expression which can be added to a pointer to a class to access a member of that class in a
.* or
->*
operation. This is done by the token:
~cpp.pm.offset : ( EXP pm, ALIGNMENT a ) -> EXP OFFSET ( a, a )Note that it is necessary to specify an alignment in order to describe the shape of the result. The value of this token is undefined if the given expression is a null pointer to data member.
A pointer to a data member of a non-virtual base class can be converted
to a pointer to a data member of a derived class. The reverse conversion
is also possible using static_cast. If the base is a
primary base class then these conversions are
trivial and have no effect. Otherwise null pointers to data members
are converted to null pointers to data members, and the non-null cases
are handled by the tokens:
~cpp.pm.cast : ( EXP pm, EXP OFFSET ) -> EXP pm ~cpp.pm.uncast : ( EXP pm, EXP OFFSET ) -> EXP pmwhere the given offset is the offset of the base class within the derived class. It is also possible to convert between any two pointers to data members using
reinterpret_cast. This conversion
is implied by the equality of representation between any two pointers
to data members and has no effect.
The only remaining operations on pointer to data members are to test one against the null pointer to data member and to compare two pointer to data members. These are represented by the tokens:
~cpp.pm.test : ( EXP pm, LABEL, NTEST ) -> EXP TOP ~cpp.pm.compare : ( EXP pm, EXP pm, LABEL, NTEST ) -> EXP TOPwhere the given
NTEST gives the comparison to be applied
and the given label gives the destination to jump to if the test fails.
In the default implementation, pointers to data members are implemented
as int. The null pointer to member is represented by
0 and the address of a class member is represented by 1 plus the offset
of the member (in bytes). Casting to and from a derived class then
correspond to adding or subtracting the base class offset (in bytes),
and pointer to member comparisons correspond to integer comparisons.
As with pointers to data members, pointers to function members and the operations on them are represented by tokens to allow for a range of implementations. All pointers to function members are represented by the same shape:
~cpp.pmf.type : () -> SHAPEThis shape will be denoted by
pmf in the description
of the following tokens. Many of the tokens take an expression which
has a shape which is a pointer to the alignment of pmf.
This will be denoted by ppmf.
There are two basic methods for constructing a pointer to a function member. The first is to take the address of a non-static member function of a class. There are two cases, depending on whether or not the member function is virtual. The non-virtual case is given by the token:
~cpp.pmf.make : ( EXP PROC, EXP OFFSET, EXP OFFSET ) -> EXP pmfwhere the first argument is the address of the corresponding function, the second argument gives any base class offset which is to be added when calling this function (to deal with inherited member functions), and the third argument is a zero offset.
For virtual functions, a pointer to function member of the form above is entered in the virtual function table for the corresponding class. The actual pointer to the virtual function member then gives a reference into the virtual function table as follows:
~cpp.pmf.vmake : ( SIGNED_NAT, EXP OFFSET, EXP, EXP ) -> EXP pmfwhere the first argument gives the index of the function within the virtual function table, the second argument gives the offset of the vptr field within the class, and the third and fourth arguments are zero offsets.
The second way of constructing a pointer to a function member is to use a null pointer to function member:
~cpp.pmf.null : () -> EXP pmf ~cpp.pmf.null2 : () -> EXP pmfFor technical reasons there are two versions of this token, although they have the same value. The first token is used in static initialisers; the second token is used in other expressions.
The cast operations on pointers to function members are more complex than those on pointers to data members. The value to be cast is copied into a temporary and one of the tokens:
~cpp.pmf.cast : ( EXP ppmf, EXP OFFSET, EXP, EXP OFFSET ) -> EXP TOP ~cpp.pmf.uncast : ( EXP ppmf, EXP OFFSET, EXP, EXP OFFSET ) -> EXP TOPis applied to modify the value of the temporary according to the given cast. The first argument gives the address of the temporary, the second gives the base class offset to be added or subtracted, the third gives the number to be added or subtracted to convert virtual function indexes for the base class into virtual function indexes for the derived class, and the fourth gives the offset of the vptr field within the class. Again, the ability to use
reinterpret_cast
to convert between any two pointer to function member types arises
because of the uniform representation of these types.
As with pointers to data members, there are tokens implementing comparisons on pointers to function members:
~cpp.pmf.test : ( EXP ppmf, LABEL, NTEST ) -> EXP TOP ~cpp.pmf.compare : ( EXP ppmf, EXP ppmf, LABEL, NTEST ) -> EXP TOPNote however that the arguments are passed by reference.
The most important, and most complex, operation is calling a function through a pointer to function member. The first step is to copy the pointer to function member into a temporary. The token:
~cpp.pmf.virt : ( EXP ppmf, EXP, ALIGNMENT ) -> EXP TOPis then applied to the temporary to convert a pointer to a virtual function member to a normal pointer to function member by looking it up in the corresponding virtual function table. The first argument gives the address of the temporary, the second gives the object to which the function is to be applied, and the third gives the alignment of the corresponding class. Now the base class conversion to be applied to the object can be determined by applying the token:
~cpp.pmf.delta : ( ALIGNMENT a, EXP ppmf ) -> EXP OFFSET ( a, a )to the temporary to find the offset to be added. Finally the function to be called can be extracted from the temporary using the token:
~cpp.pmf.func : ( EXP ppmf ) -> EXP PROCThe function call then procedes as normal.
The default implementation is that described in the ARM, where each pointer to function member is represented in the form:
struct PTR_MEM_FUNC {
short delta ;
short index ;
union {
void ( *func ) () ;
short off ;
} u ;
} ;
The delta field gives the base class offset (in bytes)
to be added before applying the function. The index
field is 0 for null pointers, -1 for non-virtual function pointers
and the index into the virtual function table for virtual function
pointers (as described below these indexes start from 1). For non-virtual
function pointers the function itself is given by the u.func
field. For virtual function pointers the offset of the vptr
field within the class is given by the u.off field.
Consider a class with no base classes:
class A {
// A's members
} ;
Each object of class A needs its own copy of the non-static
data members of A and, for polymorphic types, a means of referencing
the virtual function table and run-time type information for A.
This is accomplished using a layout of the form:
TDF has no concept of a generic pointer type, so tokens are used to
defer the representation of void * and the basic operations
on it to the target machine. The fundamental token is:
~ptr_void : () -> SHAPEwhich gives the representation of
void *. This shape
will be denoted by pv in the description of the following
tokens. It is not guaranteed that pv is a TDF pointer
shape, although normally it will be implemented as a pointer to a
suitable alignment.
The token:
~null_pv : () -> EXP pvgives the value of a null pointer of type
void *. Generic
pointers can also be converted to and from other pointers. These
conversions are represented by the tokens:
~to_ptr_void : ( ALIGNMENT a, EXP POINTER a ) -> EXP pv ~from_ptr_void : ( ALIGNMENT a, EXP pv ) -> EXP POINTER awhere the given alignment describes the destination or source pointer type. Finally a generic pointer may be tested against the null pointer or two generic pointers may be compared. These operations are represented by the tokens:
~pv_test : ( EXP pv, LABEL, NTEST ) -> EXP TOP ~cpp.pv_compare : ( EXP pv, EXP pv, LABEL, NTEST ) -> EXP TOPwhere the given
NTEST gives the comparison to be applied
and the given label gives the destination to jump to if the test fails.
(Note that ~cpp.pv_compare should have been a standard
C token but was omitted.)
Several conversions in C and C++ can only be represented by undefined TDF. For example, converting a pointer to an integer can only be represented in TDF by forming a union of the pointer and integer shapes, putting the pointer into the union and pulling the integer out. Such conversions are tokenised. Undefined conversions not mentioned below may be performed by combining those given with the standard, well-defined, conversions.
The token:
~ptr_to_ptr : ( ALIGNMENT a, ALIGNMENT b, EXP POINTER a ) -> EXP POINTER bis used to convert between two incompatible pointer types. The first alignment describes the source pointer shape while the second describes the destination pointer shape. Note that if the destination alignment is greater than the source alignment then the source pointer can be used in most TDF constructs in place of the destination pointer, so the use of
~ptr_to_ptr can be omitted (the exception
is
pointer_test which requires equal alignments). Base
class pointer conversions are examples of these well-behaved, alignment
preserving conversions.
The tokens:
~f_to_pv : ( EXP PROC ) -> EXP pv ~pv_to_f : ( EXP pv ) -> EXP PROCare