Compile go to llvm and modify IR

GOLLVM basic usage

Compile golang code to IR and run IR edit program, then compile it to binary.

Build & install

git clone https://github.com/llvm/llvm-project.git

cd llvm-project/llvm/tools
git clone https://go.googlesource.com/gollvm

cd gollvm
git clone https://go.googlesource.com/gofrontend

cd libgo
git clone https://github.com/libffi/libffi.git

git clone https://github.com/ianlancetaylor/libbacktrace.git

Build

cd llvm-project
mkdir build-gollvm
cd build-gollvm

cmake -DCMAKE_INSTALL_PREFIX=/home/zyh/Desktop/llvm_install -DCMAKE_BUILD_TYPE=Release -DLLVM_USE_LINKER=gold -G Ninja ../llvm

ninja gollvm

Install

ninja install-gollvm
export LD_LIBRARY_PATH=/home/zyh/Desktop/llvm_install/lib64
export PATH=/home/zyh/Desktop/llvm_install/bin:$PATH

Compile golang to IR

We have a simple go program

test.go

package main
  
import "fmt"

func add(x, y int) int {
    return x + y
}


func main() {
    var a int = 50000
    var b int = 4
    fmt.Println(add(a, b))
}

run test.go

go run test.go
50004

compile

llvm-goc -O0 -S -emit-llvm test.go

then we get test.ll

...
define internal i64 @main.add(i8* nest %nest.0, i64 %x, i64 %y) #0 !dbg !81 {
entry:
  %x.addr = alloca i64
  %y.addr = alloca i64
  
  ...

  %x.ld.0 = load i64, i64* %x.addr, !dbg !90
  %y.ld.0 = load i64, i64* %y.addr, !dbg !91
  %add.0 = add i64 %x.ld.0, %y.ld.0, !dbg !92
  store i64 %add.0, i64* %"$ret0", !dbg !93
  
  ...

}
...

Modify IR

We use a simple IR edit program which simply change add to sub

//...
for (auto &i_i : bb->getInstList()) {
    if (!i_i.isBinaryOp()) {
        continue;
    }
    auto *i = dyn_cast<Instruction>(&i_i);
    if (i != nullptr && i->getOpcode() == Instruction::Add) {
        Instruction *new_i = BinaryOperator::CreateSub(i->getOperand(0), i->getOperand(1));
        // replace the instruction later
        instruction_replace.push_back(i);
        instruction_new.push_back(new_i);
    }
}
//...
// replace here
for (int i = 0; i < instruction_replace.size(); i++) {
    ReplaceInstWithInst(instruction_replace[i], instruction_new[i]);
}
//...

run the program

./llvm-learn/p1/build/llvm-p1/llvm-p1 test.ll

then we get test_result.ll

...
define internal i64 @main.add(i8* nest %nest.0, i64 %x, i64 %y) #0 !dbg !81 {
entry:
  %x.addr = alloca i64
  %y.addr = alloca i64
  
  ...

  %x.ld.0 = load i64, i64* %x.addr, !dbg !90
  %y.ld.0 = load i64, i64* %y.addr, !dbg !91
  %add.0 = sub i64 %x.ld.0, %y.ld.0, !dbg !92
  store i64 %add.0, i64* %"$ret0", !dbg !93
  
  ...

}
...

Compile IR to binary

llc test_result.ll
gccgo test_result.s
./a.out

# output is 49996