llir / llvm

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

Having trouble understanding how ir.Instruction and value.Value interact #176

Closed rdeusser closed 3 years ago

rdeusser commented 3 years ago

This gist (with comments) illustrates the issue I'm having: https://gist.github.com/rdeusser/26273ba769e8290ffdcb8841b69206f0

It feels like what I need to do is somehow get a value.Value either into an ir.Instruction or a slice of them. I must be doing something wrong if I'm thinking this way. Any help would be much appreciated.

mewmew commented 3 years ago

Hi @rdeusser!

Glad to see you experimenting with LLVM IR :)

The reason you cannot use ir.Instruction directly as a value.Value is because some instruction do not produce result values, and as such do not implement the value.Value interface. For instance, the store and the fence instructions do not implement value.Value.

You can get around this by defining your own ValueInst interface, as done for instance in the uc compiler:

From uc/irgen/irgen.go:132:

// valueInst represents an instruction producing a value.
type valueInst interface {
    ir.Instruction
    value.Named
}

In compiler2.go

Just for clarity, rename local variable from inst to val (as it may for instance be a constant literal):

-inst := c.compileExpression(ret.Expression)
-return ir.NewRet(inst) // works now
+val := c.compileExpression(ret.Expression)
+return ir.NewRet(val) // works now

WIth regards to:

val := c.compileExpressionStatement(e)
block.Insts = append(block.Insts, val) // now value.Value can't be used as an ir.Instruction

@sangisos and I took a different approach to emitting IR than the one you've used here. The main idea being that you have emit functions, which take AST nodes and emit corresponding instructions to the current basic block. They in turn return a "handle" to the value of the latest emitted instruction, but since the emit functions themselves handle appending instructions to the current basic block, there is no need to append it from the caller function, as you've done on line 11 in compiler2.go.

It's probably easier to look at an example of the code than trying to grasp the explanation I just wrote. A good example would be how to lower an if-statement, see uc/irgen/lower.go:263. Included below for reference.

// ifStmt lowers the given if statement to LLVM IR, emitting code to f.
func (m *Module) ifStmt(f *Func, stmt *ast.IfStmt) {
    cond := m.cond(f, stmt.Cond)
    trueBranch := f.NewBlock("")
    end := f.NewBlock("")
    falseBranch := end
    if stmt.Else != nil {
        falseBranch = f.NewBlock("")
    }
    termCondBr := ir.NewCondBr(cond, trueBranch.Block, falseBranch.Block)
    f.curBlock.SetTerm(termCondBr)
    f.curBlock = trueBranch
    m.stmt(f, stmt.Body)
    // Emit jump if body doesn't end with return statement (i.e. the current
    // basic block is none nil).
    if f.curBlock != nil {
        termBr := ir.NewBr(end.Block)
        f.curBlock.SetTerm(termBr)
    }
    if stmt.Else != nil {
        f.curBlock = falseBranch
        m.stmt(f, stmt.Else)
        // Emit jump if body doesn't end with return statement (i.e. the current
        // basic block is none nil).
        if f.curBlock != nil {
            termBr := ir.NewBr(end.Block)
            f.curBlock.SetTerm(termBr)
        }
    }
    f.curBlock = end
}

And for further reference, see lowering of expressions in uc/irgen/lower.go:356:

// expr lowers the given expression to LLVM IR, emitting code to f.
func (m *Module) expr(f *Func, expr ast.Expr) value.Value {
    switch expr := expr.(type) {
    case *ast.BasicLit:
        return m.basicLit(f, expr)
    case *ast.BinaryExpr:
        return m.binaryExpr(f, expr)
    case *ast.CallExpr:
        return m.callExpr(f, expr)
    case *ast.Ident:
        return m.identUse(f, expr)
    case *ast.IndexExpr:
        return m.indexExprUse(f, expr)
    case *ast.ParenExpr:
        return m.expr(f, expr.X)
    case *ast.UnaryExpr:
        return m.unaryExpr(f, expr)
    default:
        panic(fmt.Sprintf("support for type %T not yet implemented", expr))
    }
}

Note, neither @sangisos nor I have looked at the code of the uc compiler for quite some time, so there may very well be better ways to do this. However, you can perhaps use it to get some inspiration at least.

Wish you all the best and happy hacking!

Cheers, Robin

rdeusser commented 3 years ago

Hi @mewmew!

This helps a lot. Thanks!

mewmew commented 3 years ago

Hi @mewmew!

This helps a lot. Thanks!

@rdeusser glad to help :)

If you decide to open source your work later on, feel free to share a link to the repo with us! Would be happy to see what you're working on, and should you wish, also add a link to your repo on the Users list; so other can take inspiration from the examples.

Cheers, Robin