Ramble Ramble

Learning everyday

.cpp/.h to Swift AST Flow

In order for the swift compiler to properly understand cxx it needs to be able to read and process .cpp and .h files. Instead of writing all the code necessary to do this, the swift compiler embeds into itself the clang compiler (clang is llvm backed CXX compiler which is also written in CXX). It then needs to map the created clang AST (Abstract Syntax Tree) into a valid swift AST.

We will start the journey of interop in the ClangImporter.cpp class found here.

This file is long (just shy of 6k lines!) but in essence it’s the orchestrator for cxx interop.

ClangImporter

Like many classes in the swift compiler, ClangImporter has a create method which is used to actually build the class (its actual constructor is private). We can find this method here (.h here). Here is the signature:

std::unique_ptr<ClangImporter> ClangImporter::create( ASTContext &ctx, std::string swiftPCHHash, DependencyTracker *tracker, DWARFImporterDelegate *dwarfImporterDelegate )

Looking at this method may seem scary as all but 1 of the passed in arguments seem foreign. The three important parameters here (in regards to the importer) are the ASTContext, DependencyTracker and DWARFImporterDelegate . All 3 are used in the first line of the function to create the actual ClangImporter

std::unique_ptr<ClangImporter> importer{ new ClangImporter(ctx, tracker, dwarfImporterDelegate)};

ASTContext (docstring)

We will go into this more in the future but I want to give a quick rundown on what the ASTContext is given how important it is in the swift compiler.

In swift, when creating global/static variables, you generally need to store them in a “Context“ (Think Arena allocation). In our case we are passing in an ASTContext (meaning this Context will exist while the swift AST exists). A Context‘s job is to allocate memory for new objects and keep objects ”alive“ until the Context itself is destroyed at which time all objects added to the context are destroyed as well.

For example, in swift, strings are generally stored in an object called a StringRef instead of an std::string. A StringRef acts like std::string but its lifetime is determined by the “Context” which it belongs to. For that reason creating a StringRef and not assigning it to a context is akin to a “dangling pointer“. Swift makes creating certain objects easy, for example: ASTContext::getIdentifier(StringRef) which will create an Identifier and assign it to the ASTContext for you.

DependencyTracker (docstring)

Generally created in frontend.cpp here the dependency tracker is used to help use find other modules which the current module we are compiling depend on.

DWARFImporterDelegate (docstring)

DWARF (Debugging With Arbitrary Record Format) is a file format used by debuggers. This delegate is used to synthesize clang Decls from debug info.

How ClangImporter is used

The ClangImporter inherits from ClangModuleLoader (here) which inherits from ModuleLoader (here). This is important because now that ClangImporter inherits from ModuleLoader, it can be added (using ASTContext::addModuleLoader) to the ASTContext like other ModuleLoaders.

We now have a better picture of how cxx interop fits into the Swift compiler. When adding cxx interop the idea was to add a new ModuleLoader which understood cxx and could return swift versions of those cxx decls when requested (ClangModuleLoader). Adding new ModuleLoaders occurs here in frontend.cpp, our ClangImporter::create function occurs a few lines later (here) and finally it is added as a module loader here. ASTContext exposes the clang module loader explicitly using ClangModuleLoader ASTContext::getClangModuleLoader which in turn exposes all the necessary methods for perform interop to the rest of the swift compiler!

Tagged with: