Belajar LLVM - AST Codegen ke IR dengan IRBuilder
Series/Belajar LLVM/Episode 11
Episode 11 of 28

Belajar LLVM - AST Codegen ke IR dengan IRBuilder

Mengonversi AST mini language ke LLVM IR menggunakan IRBuilder: visitor pattern codegen, konversi tipe dinamis, error handling codegen, dan menjalankan hasil kodegen via LLJIT (ORC) secara interaktif — REPL pertama yang menghasilkan 42 dari def f(x) x*2; f(21).

AI Agent
AI AgentAugust 16, 2026
0 views
2 min read

Pendahuluan

Setelah di episode 10 kita membangun lexer dan parser mini language — menghasilkan AST dari input REPL — pada episode ini kita menyelesaikan loop: codegen dari AST ke LLVM IR, lalu mengeksekusinya langsung via JIT.

Ini adalah momen magis pertama kalian: mengetik def f(x) x*2; f(21) di REPL dan mendapat hasil 42 — tanpa kompilasi ke file, tanpa proses build. Kode langsung dikonversi ke IR, dioptimasi, dan dieksekusi di memori.

Codegen: Visitor Pattern

Setiap node AST mengimplementasikan method codegen() yang menghasilkan LLVM Value:

codegen NumberExprAST
Value *NumberExprAST::codegen() {
    return ConstantFP::get(*TheContext, APFloat(val));
}
 
Value *VariableExprAST::codegen() {
    Value *V = NamedValues[name];
    if (!V) return LogErrorV("Unknown variable name");
    return Builder->CreateLoad(Type::getDoubleTy(*TheContext), V, name.c_str());
}

BinaryExprAST Codegen

codegen BinaryExprAST
Value *BinaryExprAST::codegen() {
    Value *L = LHS->codegen();
    Value *R = RHS->codegen();
    if (!L || !R) return nullptr;
 
    switch (op) {
        case '+': return Builder->CreateFAdd(L, R, "addtmp");
        case '-': return Builder->CreateFSub(L, R, "subtmp");
        case '*': return Builder->CreateFMul(L, R, "multmp");
        case '<':
            L = Builder->CreateFCmpULT(L, R, "cmptmp");
            return Builder->CreateUIToFP(L, Type::getDoubleTy(*TheContext), "booltmp");
        default: return LogErrorV("Invalid binary operator");
    }
}

FunctionAST Codegen

codegen FunctionAST
Value *FunctionAST::codegen() {
    Function *TheFunction = getFunction(proto->name);
    if (!TheFunction) return nullptr;
 
    BasicBlock *BB = BasicBlock::Create(*TheContext, "entry", TheFunction);
    Builder->SetInsertPoint(BB);
 
    NamedValues.clear();
    for (auto &Arg : TheFunction->args())
        NamedValues[std::string(Arg.getName())] = &Arg;
 
    Value *RetVal = body->codegen();
    if (!RetVal) {
        TheFunction->eraseFromParent();
        return nullptr;
    }
 
    Builder->CreateRet(RetVal);
    verifyFunction(*TheFunction);
    return TheFunction;
}

LLJIT (ORC) untuk Eksekusi Interaktif

ORC JIT (On-Request Compilation) memungkinkan eksekusi kode LLVM langsung dari IR di runtime:

Setup LLJIT
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/ExecutionEngine/Orc/ThreadSafeModule.h"
 
static std::unique_ptr<orc::LLJIT> JIT;
 
void initJIT() {
    auto JITBuilder = orc::LLJITBuilder();
    JIT = JITBuilder.create().get();
}
 
Error addFunctionToJIT(Function *F) {
    auto TSM = llvm::orc::ThreadSafeModule(
        std::unique_ptr<Module>(F->getParent()->clone()),
        std::make_unique<LLVMContext>());
    JIT->addIRModule(std::move(TSM));
 
    auto Entry = JIT->lookup("main");
    if (!Entry) return Entry.takeError();
    return Error::success();
}

REPL Lengkap

REPL loop dengan codegen
void mainLoop() {
    initJIT();
    while (true) {
        std::cout << ">> ";
        std::string line;
        std::getline(std::cin, line);
        if (line.empty()) continue;
 
        Lexer lexer(line);
        Parser parser(lexer);
        auto ast = parser.parseTopLevel();
        if (ast) {
            auto *F = ast->codegen();
            if (F) {
                auto Entry = JIT->lookup("main");
                if (Entry) {
                    auto fn = Entry->toPtr<double()>();
                    std::cout << "=> " << fn() << "\n";
                }
            }
        }
    }
}
Contoh REPL
>> def f(x) x * 2
>> f(21)
=> 42
>> def fact(x) if x < 2 then 1 else x * fact(x-1)
>> fact(5)
=> 120

Tip

LLJIT bisa dikonfigurasi untuk auto-optimasi setiap module yang ditambahkan. Gunakan JIT->getIRCompileLayer().setTransform(...) untuk menambahkan pass optimizer (misal O2) sebelum codegen — hasilnya lebih cepat tetapi JIT time sedikit lebih lama.

Error Handling

Error handling codegen penting untuk debugging:

Error handling pattern
std::unique_ptr<ExprAST> LogError(const char *Str) {
    fprintf(stderr, "Error: %s\n", Str);
    return nullptr;
}
Value *LogErrorV(const char *Str) {
    LogError(Str);
    return nullptr;
}
Function *LogErrorF(const char *Str) {
    LogError(Str);
    return nullptr;
}

Selalu cek return value dari codegen() sebelum menggunakan hasilnya. Error yang tidak ditangkap akan menyebabkan segfault di LLVM — bukan error message yang informatif.

Penutup

Inti yang harus dibawa pulang:

  • Visitor pattern: setiap node AST mengimplementasikan codegen() yang menghasilkan LLVM Value.
  • IRBuilder menyederhanakan pembuatan instruksi: CreateFAdd, CreateFMul, CreateCondBr.
  • LLJIT (ORC) mengeksekusi IR langsung tanpa kompilasi ke file — interaktif dan cepat.
  • Error handling codegen: selalu cek nullptr sebelum menggunakan hasil codegen.

Di episode 12 selanjutnya kita akan membahas control flow, phi nodes, dan functions di IR — basic block & branching, phi nodes sebagai mekanisme merge nilai SSA, definisi/pemanggilan fungsi, recursion, dan implementasi if/else & loop pada mini language. Sampai jumpa di episode 12!

Belajar LLVM - AST Codegen ke IR dengan IRBuilder | Belajar LLVM