llir / llvm

Library for interacting with LLVM IR in pure Go.
https://llir.github.io/document/
BSD Zero Clause License
1.18k stars 78 forks source link

How to use Function Pointers? #215

Closed RedCubeDev-ByteSpace closed 2 years ago

RedCubeDev-ByteSpace commented 2 years ago

Hello there 👋

Ive been working on a compiler for a couple weeks and started work on some object oriented constructs.
For that I have written all the necessary code in C using structs and functions. I would now like use those structs in my go-compiled program. (I assume using external structs works the same way as using external functions (just declaring it and the linker takes care of the rest))

The problem I am currently facing is that these structs contain function pointers but sadly I have no idea how to create a function pointer type and use it for a type definition. I also couldnt find any info online, in the linked documentation, or in any other github issues.

Also this project is really lacking in (noob friendly) documentation haha

I hope someone here can help me out ^^

mewmew commented 2 years ago

Hi @RedCubeDev-ByteSpace!

Glad to see you are getting into compiler design and development! It's a great passion and you will hopefully have a lot of fun implementing your own compiler :)

Given the following input C source file:

typedef int (*FooFunc)(int x, int y);
typedef double (*BarFunc)(double v);

typedef struct {
    FooFunc foo_func;
    BarFunc bar_func;
} StructType;

int foo(int x, int y) {
    return x + y;
}

double bar(double v) {
    return 2.0*v;
}

int main() {
    StructType baz;
    baz.foo_func = foo;
    baz.bar_func = bar;
    double result = baz.bar_func(3.0);
    return baz.foo_func(12, 30);
}

And the corresponding LLVM IR (by running the command clang -S -emit-llvm -o foo.ll foo.c):

; ModuleID = 'foo.c'
source_filename = "foo.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"

%struct.StructType = type { i32 (i32, i32)*, double (double)* }

; Function Attrs: noinline nounwind optnone sspstrong uwtable
define dso_local i32 @foo(i32 %0, i32 %1) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  store i32 %0, i32* %3, align 4
  store i32 %1, i32* %4, align 4
  %5 = load i32, i32* %3, align 4
  %6 = load i32, i32* %4, align 4
  %7 = add nsw i32 %5, %6
  ret i32 %7
}

; Function Attrs: noinline nounwind optnone sspstrong uwtable
define dso_local double @bar(double %0) #0 {
  %2 = alloca double, align 8
  store double %0, double* %2, align 8
  %3 = load double, double* %2, align 8
  %4 = fmul double 2.000000e+00, %3
  ret double %4
}

; Function Attrs: noinline nounwind optnone sspstrong uwtable
define dso_local i32 @main() #0 {
  %1 = alloca i32, align 4
  %2 = alloca %struct.StructType, align 8
  %3 = alloca double, align 8
  store i32 0, i32* %1, align 4
  %4 = getelementptr inbounds %struct.StructType, %struct.StructType* %2, i32 0, i32 0
  store i32 (i32, i32)* @foo, i32 (i32, i32)** %4, align 8
  %5 = getelementptr inbounds %struct.StructType, %struct.StructType* %2, i32 0, i32 1
  store double (double)* @bar, double (double)** %5, align 8
  %6 = getelementptr inbounds %struct.StructType, %struct.StructType* %2, i32 0, i32 1
  %7 = load double (double)*, double (double)** %6, align 8
  %8 = call double %7(double 3.000000e+00)
  store double %8, double* %3, align 8
  %9 = getelementptr inbounds %struct.StructType, %struct.StructType* %2, i32 0, i32 0
  %10 = load i32 (i32, i32)*, i32 (i32, i32)** %9, align 8
  %11 = call i32 %10(i32 12, i32 30)
  ret i32 %11
}

attributes #0 = { noinline nounwind optnone sspstrong uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }

!llvm.module.flags = !{!0, !1, !2, !3, !4}
!llvm.ident = !{!5}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 7, !"PIC Level", i32 2}
!2 = !{i32 7, !"PIE Level", i32 2}
!3 = !{i32 7, !"uwtable", i32 1}
!4 = !{i32 7, !"frame-pointer", i32 2}
!5 = !{!"clang version 13.0.1"}

We can see that the struct type containing the two function pointers is as follows:

%struct.StructType = type { i32 (i32, i32)*, double (double)* }

To produce a corresponding struct type using the llir/llvm API, the type has to be the same, both structurally (have the same struct fields with the same types) and the same type name (i.e. %struct.StructType in this case).

package main

import (
    "fmt"

    "github.com/llir/llvm/ir/types"
)

func main() {
    i32RetType := types.I32
    i32XParamType := types.I32
    i32YParamType := types.I32
    fooFuncType := types.NewFunc(i32RetType, i32XParamType, i32YParamType)
    doubleRetType := types.Double
    doubleVParamType := types.Double
    barFuncType := types.NewFunc(doubleRetType, doubleVParamType)
    structType := types.NewStruct(fooFuncType, barFuncType)
    structType.SetName("struct.StructType")
    fmt.Println(structType)
}

The above Go program will output:

%struct.StructType

In particular, take a look at the documentation of ir/types.NewFunc.

Wish you all the best and happy coding!

Cheers, Robin

RedCubeDev-ByteSpace commented 2 years ago

Oh wow thanks for the quick reply!
I see, alright thank you a lot for taking the time to explain this to me :D

mewmew commented 2 years ago

Glad the explanation helped! :)

I'll close this issue for now, if you wonder something in the future, feel free to open a new issue or re-open this one.

Happy coding!

Cheers, Robin