Chapter 8
ABI Related APIs

This section gives additional procedural macro API components that are important to creating classes and controlling the ABI.

8.1 User Type and Module API

Within the class macro it is necessary to get some basic properties on data member types and the parent class. In particular it is necessary to determine if the type is a user type with any special methods such as a default constructor or destructor. The API for user types and modules is shown in Figure 8.1.


UserType * user_type_info(Syntax *, const Environ *)

Constructor: Module * module_info(Syntax *, const Environ *)

Type UserType with methods:

Module * module()

bool have_default_constructor()

bool have_copy_constructor()

bool have_assign()

bool have_destructor()

Type Module with methods:

bool have_symbol(const Syntax *)

Figure 8.1: User type and module API.

The constructors user_type_info and module_info get the corresponding symbol from a symbol name. From a user_type it is also possible to get the underlying module using the module method.

The have_* user type methods are used to check if a data-member type has any special methods. The class macro uses this information when building the corresponding special method. For example if any the data-members have the assign method is is necessary to create an assign method for the class.

8.2 User Type Builder

Due to the need to get information about the user type as it is being built, the class macro builds the user type directly and then returns a syntax object with the compiled syntax object embedded directly. The builder API is shown in Figure 8.2.

Type UserTypeBuilder with constructor:

UserTypeBuilder * new_user_type_builder(Syntax * name, Environ * env)

and methods:

void add(Syntax *)

Syntax * to_syntax()

bool have_default_constructor()

bool have_copy_constructor()

bool have_assign()

bool have_destructor()

and members (read only):

Environ * env

UserType * user_type

Figure 8.2: User type builder API.

A new builder is created using new_user_type_builder. Components are added using the add method. Finally, the to_syntax method is used to finalize the user type and return a syntax object with the compiled user type embed within.

The have_* methods are used for querying the user type as it is being built. They are needed because, due to overloading, it is difficult for the class macro to determine if a constructor or assignment operator is provided that satisfies the requirements of a copy constructor or copy assignment operator, restively. Thus, after all the methods are added to the user type, the class macro uses these methods to check for the existence of the special methods and can act appropriately.

The user type builder also exposes several read only members. The most important one is the local environment inside the module. This environment is needed when partly expanding class components, for example, in the following code:

  typedef const char * iterator;
  iterator begin();

the second line, will not expand correctly unless the iterator type is in the environment.

8.3 The ABI Switch

Since class layout is a key component to the ABI, a new ABI can be created by extending (or overriding) the class macro and then remapping the class syntax object to use the new macro. Another way to define a new ABI is to register the ABI so that it can be used with the ABI switch. The ABI switch is an extension of C++ extern with an additional part for the ABI. For example:

  extern "C++" : "gcc"
  class C {...};

causes the class C to use the “gcc” ABI. In addition to class layout, the ABI switch also controls name managing and other key components of the ABI, which can differ between compilers.

A class macro is registered with the ABI switch by compiling it into into a macro library and defining the symbols _abi_list and _abi_list_size. The _abi_list variable is an array of AbiInfo and _abi_list_size is the array size. The struct AbInfo is defined as:

  struct AbiInfo {
    const char * abi_name;
    MangleFun mangler;
    MacroLikeFun parse_class;
    const char * module_name;
    Module * module;

The abi_name member is the name of the ABI, and parse_class points to the macro function defining the class. The mangler member is part of the mangler ABI and will be described the next section.

Class layout and mangling are two important parts of the ABI. Another important part is the implementation of new and delete. To support any ABI specific implementations a module name can be provided. Any symbols is this module will shadow any global symbols when the ABI is in effect; thus ABI specific new and delete macros can be defined. In addition the ABI info is tied to a user type so a class is always allocated and deleted with the class ABIs new and delete.

For example the macro library implementing the “gcc” ABI has the following lines:

  unsigned _abi_list_size = 1;
  AbiInfo _abi_list[1] = {{"gcc", NULL, parse_class_gcc_abi,
                           "gcc_abi_info", NULL}};

with the following lines in the header file:

  module gcc_abi_info {
    macro alloc(type, size) {...}
    macro free(type, ptr) {...}

where alloc and free are called by the new and delete primitives, receptively.

The final member module is filled in by ZL when the macro library is read in, by looking for a module with the name module_name.

8.4 Mangler API

The final aspect of the ABI that ZL can control is the mangling scheme. The API to implement the alternative manglers is part of ABI switch implementation just described. The function type MangleFun is defined as:

  StringObj * (*MangleFun)(Symbol *)

The mangler takes a symbol and transforms it into a string of the form of a pointer to StringObj. The string object is expected to build up a string using the StringBuf and then call the freeze method, which returns a StringObj. An overview of the StringBuf class is given in Figure 8.3.

  class StringBuf {
      StringBuf(const char * s);
      StringBuf(const char * s, unsigned size);
      StringBuf(const StringBuf & other);
      StringBuf & operator= (const char * other);
      StringBuf & operator= (const StringBuf & other);
      StringBuf & append(char * start, char * stop);
      StringBuf & operator+= (const char * s);
      StringBuf & operator+= (const StringBuf & s);
      StringBuf & prepend(const char * str);
      int printf(const char * format, ...);
      size_t size() const;
      bool empty() const;
      char * data();
      StringObj * freeze();

Figure 8.3: Overview of the StringBuf class.

In order to transform the string the mangler needs access to a large number of properties about the symbol. The most important of these properties is the parameter types for function symbols as they are the primary components of the mangled name. An overview of the API used for getting symbol properties is given in Figure 8.4.

Type Symbol with methods:

const char * name()

const char * uniq_name()

Type * type()

FunType * fun_type()

Syntax * prop(UnmarkedSyntax * prop)

Type Type with methods:

Type * subtype()

int qualifiers()

bool is_scalar()

bool is_qualified()

bool is_pointer()


TypeQualifier_CONST    = 1

TypeQualifier_VOLATILE = 2

TypeQualifier_RESTRICT = 4

Type FunType with methods:

Type * ret_type()

unsigned num_parms(const FunType *)

Type * parm_type(unsigned num)

Figure 8.4: Overview of the symbol API

Once the mangler function is defined it is necessary to register it with ABI switch. Different components of the ABI may be given in different libraries, and any NULL fields will simply be left alone if there where defined elsewhere for that ABI. For example, the GCC mangler is defined with the following line:

  unsigned _abi_list_size = 1;
  AbiInfo _abi_list[1] = {{"gcc", to_external_name, NULL, NULL, NULL}};

Converted From LaTeX using TeX4ht. PDF Version