The parameters of a function also need some consideration. First, we need to map the types of the source language to an LLVM type. As tinylang currently has only two types, this is easy:


llvm::Type *CGModule::convertType(TypeDeclaration *Ty) {
  if (Ty->getName() == “INTEGER”)
    return Int64Ty;
  if (Ty->getName() == “BOOLEAN”)
    return Int1Ty;
  llvm::report_fatal_error(“Unsupported type”);
}

Int64Ty, Int1Ty, and VoidTy are class members that hold the type representation of the i64, i1, and void LLVM types.

For a formal parameter passed by reference, this is not enough. The LLVM type of this parameter is a pointer. However, when we want to use the value of the formal parameter, we need to know the underlying type. This is controlled by the HonorReference flag, which has a default value of true. We generalize the function and take the formal parameter into account:


llvm::Type *CGProcedure::mapType(Decl *Decl,
                                 bool HonorReference) {
  if (auto *FP = llvm::dyn_cast<FormalParameterDeclaration>(
          Decl)) {
    if (FP->isVar() && HonorReference)
      return llvm::PointerType::get(CGM.getLLVMCtx(),
                                    /*AddressSpace=*/0);
    return CGM.convertType(FP->getType());
  }
  if (auto *V = llvm::dyn_cast<VariableDeclaration>(Decl))
    return CGM.convertType(V->getType());
  return CGM.convertType(llvm::cast<TypeDeclaration>(Decl));
}

With these helpers at hand, we can create the LLVM IR function.

Creating the LLVM IR function

To emit a function in LLVM IR, a function type is needed, which is similar to a prototype in C. Creating the function type involves mapping the types and then calling the factory method to create the function type:


llvm::FunctionType *CGProcedure::createFunctionType(
    ProcedureDeclaration *Proc) {
  llvm::Type *ResultTy = CGM.VoidTy;
  if (Proc->getRetType()) {
    ResultTy = mapType(Proc->getRetType());
  }
  auto FormalParams = Proc->getFormalParams();
  llvm::SmallVector<llvm::Type *, 8> ParamTypes;
  for (auto FP : FormalParams) {
    llvm::Type *Ty = mapType(FP);
    ParamTypes.push_back(Ty);
  }
  return llvm::FunctionType::get(ResultTy, ParamTypes,
                                 /*IsVarArgs=*/false);
}

Based on the function type, we also create the LLVM function. This associates the function type with the linkage and the mangled name:


llvm::Function *
CGProcedure::createFunction(ProcedureDeclaration *Proc,
                            llvm::FunctionType *FTy) {
  llvm::Function *Fn = llvm::Function::Create(
      Fty, llvm::GlobalValue::ExternalLinkage,
      CGM.mangleName(Proc), CGM.getModule());

The getModule() method returns the current LLVM module, which we’ll set up a bit later.

With the function created, we can add some more information about it:

  • First, we can give the parameter’s names. This makes the IR more readable.
  • Second, we can add attributes to the function and to the parameters to specify some characteristics. As an example, we will do this for parameters passed by reference.

At the LLVM level, these parameters are pointers. But from the source language design, these are very restricted pointers. Analogous to references in C++, we always need to specify a variable for a VAR parameter. So, by design, we know that this pointer will never be null and that it is always dereferenceable, meaning that we can read the value that’s being pointed to without risking a general protection fault. Also, by design, this pointer cannot be passed around – in particular, there are no copies of the pointer that outlive the call to the function. Therefore, the pointer is said to not be captured.

The llvm::AttributeBuilder class is used to build the set of attributes for a formal parameter. To get the storage size of a parameter type, we can simply query the data layout object:


  for (auto [Idx, Arg] : llvm::enumerate(Fn->args())) {
    FormalParameterDeclaration *FP =
        Proc->getFormalParams()[Idx];
    if (FP->isVar()) {
      llvm::AttrBuilder Attr(CGM.getLLVMCtx());
      llvm::TypeSize Sz =
          CGM.getModule()->getDataLayout().getTypeStoreSize(
              CGM.convertType(FP->getType()));
      Attr.addDereferenceableAttr(Sz);
      Attr.addAttribute(llvm::Attribute::NoCapture);
      Arg.addAttrs(Attr);
    }
    Arg.setName(FP->getName());
  }
  return Fn;
}

With that, we have created the IR function. In the next section, we’ll add the basic blocks of the function body to the function.

Leave a Reply

Your email address will not be published. Required fields are marked *