In general, you translate expressions, as shown in Chapter 2, The Structure of a Compiler. The only interesting part is how to access variables. The previous section treated local variables, but there are other kinds of variables we can consider. Let’s discuss what we need to do:

  • For a local variable of the procedure, we use the readLocalVariable() and writeLocalVariable() methods from the previous section.
  • For a local variable in an enclosing procedure, we need a pointer to the frame of the enclosing procedure. This will be handled later in this chapter.
  • For a global variable, we generate load and store instructions.
  • For a formal parameter, we have to differentiate between passing by value and passing by reference (the VAR parameter in tinylang). A parameter that’s passed by value is treated as a local variable, and a parameter passed by reference is treated as a global variable.

Putting it all together, we get the following code for reading a variable or formal parameter:
llvm::Value *CGProcedure::readVariable(llvm::BasicBlock *BB,
                                       Decl *D) {
  if (auto *V = llvm::dyn_cast<VariableDeclaration>(D)) {
    if (V->getEnclosingDecl() == Proc)
      return readLocalVariable(BB, D);
    else if (V->getEnclosingDecl() ==
             CGM.getModuleDeclaration()) {
      return Builder.CreateLoad(mapType(D),
                                CGM.getGlobal(D));
    } else
      llvm::report_fatal_error(
          “Nested procedures not yet supported”);
  } else if (auto *FP =
                 llvm::dyn_cast<FormalParameterDeclaration>(
                     D)) {
    if (FP->isVar()) {
      return Builder.CreateLoad(mapType(FP, false),
                                FormalParams[FP]);
    } else
      return readLocalVariable(BB, D);
  } else
    llvm::report_fatal_error(“Unsupported declaration”);
}

Writing to a variable or formal parameter is symmetrical – we just need to exchange the method to read with the one to write and use a store instruction instead of a load instruction.

Next, these functions are applied while generating the IR code for the functions.

Emitting the IR code for a function

Most of the IR code will live in a function. A function in IR code resembles a function in C. It specifies in the name, the types of parameters, the return value, and other attributes. To call a function in a different compilation unit, you need to declare the function. This is similar to a prototype in C. If you add basic blocks to the function, then you define the function. We will do all this in the next few sections, but first, we will discuss the visibility of symbol names.

Leave a Reply

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