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

Typedefs and Struct types #193

Closed IbrahimFadel closed 3 years ago

IbrahimFadel commented 3 years ago

I'm trying to creat typedefs and struct types, but ran into some issues. Here is code the replicate the issues:

package main

import (
    "fmt"

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

func main() {
    mod := ir.NewModule()

    structName := "Foo"

    structTy := types.StructType{TypeName: structName}

    structTy.Fields = append(structTy.Fields, types.I32)
    structTy.Fields = append(structTy.Fields, types.I32)
    structTy.Fields = append(structTy.Fields, types.I32)

    someTypeName := mod.NewTypeDef("SomeTypeName", types.I32)

    fn := mod.NewFunc("main", types.I32)
    bb := fn.NewBlock("entry")

    bb.NewAlloca(someTypeName)
    bb.NewAlloca(&structTy)

    bb.NewRet(constant.NewInt(types.I32, 0))

    fmt.Println(mod)
}

And this is what the module looks like:

%SomeTypeName = type i32

define %SomeTypeName @main() {
entry:
    %0 = alloca %SomeTypeName
    %1 = alloca %Foo
    ret %SomeTypeName 0
}

Firstly, when i do someTypeName := mod.NewTypeDef("SomeTypeName", types.I32), I expect that it only uses %SomeTypeName when I explicitly specify to use it. But instead, whenever it sees an i32 it replaces it with %SomeTypeName (this is shown by ret %SomeTypeName 0). If this is expected behaviour, I would ask what can I use instead?

Secondly, when I create the struct type, I expect it to create a type at the top of the module like: %Foo = type { i32, i32, i32 } and then use %Foo in the alloca instruction. But there is no type definition for Foo, it just references it so this won't compile. Again, I don't know if this is expected behaviour or not, how can I get the behaviour I'm looking for?

I'm currently porting my C++ code to Go, and with LLVM's C++ bindings I got the behaviour I was expecting automatically, but perhaps I'm using these bindings incorrectly.

dannypsnl commented 3 years ago

https://llir.github.io/document/user-guide/types/#structure

IbrahimFadel commented 3 years ago

Sorry, i didn't see that document. That answers my question about the struct type, but is there anything I can do about %SomeTypeName replacing any instance of the i32 type?

dannypsnl commented 3 years ago

minimal replacing example

package main

import (
    "fmt"

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

func main() {
    mod := ir.NewModule()

    someTypeName := mod.NewTypeDef("SomeTypeName", types.I32)

    fn := mod.NewFunc("main", types.I32)
    bb := fn.NewBlock("entry")

    bb.NewAlloca(someTypeName)

    bb.NewRet(constant.NewInt(types.I32, 0))

    fmt.Println(mod)
}

produces:

%SomeTypeName = type i32

define %SomeTypeName @main() {
entry:
    %0 = alloca %SomeTypeName
    ret %SomeTypeName 0
}

@mewmew I believe this is a bug.

mewmew commented 3 years ago

Is the issue that it replaces all i32 types with %SomeTypeName?

The reason for this is that types.I32 is being defined as %SomeTypeName in this call:

mod.NewTypeDef("SomeTypeName", types.I32)

Note, types.I32 is a global variable, and ir.Module.NewTypeDef sets the name of the given type. Thus, all uses of the global variable types.I32 are given the name %SomeTypeName. This is working as intended, at least by the design of the ir/types library.

From llir/llvm/ir/module_type.go:

func (m *Module) NewTypeDef(name string, typ types.Type) types.Type {
    typ.SetName(name)
    m.TypeDefs = append(m.TypeDefs, typ)
    return typ
}

If you want to add a new type def but not replace all instances of i32, then do as follows (notice the line with types.NewInt(32)):

package main

import (
    "fmt"

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

func main() {
    mod := ir.NewModule()

    someTypeName := mod.NewTypeDef("SomeTypeName", types.NewInt(32)) // <-- notice this line

    fn := mod.NewFunc("main", types.I32)
    bb := fn.NewBlock("entry")

    bb.NewAlloca(someTypeName)

    bb.NewRet(constant.NewInt(types.I32, 0))

    fmt.Println(mod)
}

Which produces the following LLVM IR:

%SomeTypeName = type i32

define i32 @main() {
entry:
    %0 = alloca %SomeTypeName
    ret i32 0
}
mewmew commented 3 years ago

@IbrahimFadel, on a side note. Glad to see you experimenting with LLVM :)

Curious what you are using type names for. If you are using them just to experiment and test concepts that's great. If you want to use type names to implement functionality which depend on the names, then just wanted to give a quick heads up. In LLVM IR the type name of struct types is used for type identity in type equality checks. That is, two identical structs with different names are considered different types. For all other types, type names are not used for type equality (all other types are checked by structural type equality). That is, the type name of all types except structs are only used as a type name alias, and the LLVM optimizer is free to remove such type names whenever and however it wishes and replace those with their underlying types (and it often does).

Just wanted to let you know this, so you don't start depending on a functionality that the official LLVM compiler will prune away.

WIsh you happy coding adventures and a great summer!

Cheers, Robin

IbrahimFadel commented 3 years ago

@mewmew Thanks for the working example.

I'm using the type names for typedefs in my toy compiler. So similar to go you can do something like this:

type Foo i32
type Bar struct {
  ...
}

I just wanted to solve that issue of all instances of i32 being replaced because I want to emit IR that makes sense given the source, so ideally it would preserve the type declaration's name, but only in the places where you actually use that type. But the functionality doesn't depend on it so it's all good.

Again, thanks for your help, and have a great summer yourself!

mewmew commented 3 years ago

I'm using the type names for typedefs in my toy compiler.

Cool!

Again, thanks for your help, and have a great summer yourself!

Most happy to help! Best of luck with pi-lang and happy hacking!

Cheers, Robin

mewmew commented 3 years ago

@IbrahimFadel if you want pi-lang to appear on the Users section of the llir/llvm repo, just send a PR to update the README :) See #187 for an example PR.

IbrahimFadel commented 3 years ago

Oh wow that would be great! Once i've made some more progress in my go rewrite, and i'll be happy to make a PR :)

mewmew commented 3 years ago

Oh wow that would be great! Once i've made some more progress in my go rewrite, and i'll be happy to make a PR :)

Lovely :)

P.S. sent you a tiny PR at IbrahimFadel/pi-lang#8.