ginger/vm/vm.go

151 lines
3.2 KiB
Go
Raw Normal View History

package vm
import (
2017-02-11 20:35:02 +00:00
"errors"
"fmt"
"sync"
"github.com/mediocregopher/ginger/lang"
"llvm.org/llvm/bindings/go/llvm"
)
// Val holds onto a value which has been created within the VM
type Val struct {
v llvm.Value
}
// Module contains a compiled set of code which can be run, dumped in IR form,
// or compiled. A Module should be Dispose()'d of once it's no longer being
// used.
type Module struct {
2017-02-11 20:35:02 +00:00
b llvm.Builder
m llvm.Module
mainFn llvm.Value
buildCmds []buildCmd
}
var initOnce sync.Once
// Build creates a new Module by compiling the given Term as code
func Build(t lang.Term) (*Module, error) {
initOnce.Do(func() {
llvm.LinkInMCJIT()
llvm.InitializeNativeTarget()
llvm.InitializeNativeAsmPrinter()
})
mod := &Module{
b: llvm.NewBuilder(),
m: llvm.NewModule(""),
}
2017-02-11 20:35:02 +00:00
mod.buildCmds = buildCmds(mod)
2017-02-11 20:35:02 +00:00
var err error
if mod.mainFn, err = mod.buildFn(t); err != nil {
mod.Dispose()
return nil, err
}
if err := llvm.VerifyModule(mod.m, llvm.ReturnStatusAction); err != nil {
mod.Dispose()
return nil, fmt.Errorf("could not verify module: %s", err)
}
return mod, nil
}
// Dispose cleans up all resources held by the Module
func (mod *Module) Dispose() {
// TODO this panics for some reason...
//mod.m.Dispose()
//mod.b.Dispose()
}
2017-02-11 20:35:02 +00:00
func (mod *Module) matchingBuildCmd(t lang.Term) (buildCmd, error) {
for _, cmd := range mod.buildCmds {
if !cmd.matches(t) {
continue
}
return cmd, nil
}
2017-02-11 20:35:02 +00:00
return buildCmd{}, fmt.Errorf("unknown compiler command %v", t)
}
func (mod *Module) inType(t lang.Term) (llvm.Type, error) {
cmd, err := mod.matchingBuildCmd(t)
if err != nil {
return llvm.Type{}, err
}
2017-02-11 20:35:02 +00:00
return cmd.inType(t.(lang.Tuple)[1])
}
func (mod *Module) outType(t lang.Term) (llvm.Type, error) {
cmd, err := mod.matchingBuildCmd(t)
if err != nil {
return llvm.Type{}, err
}
2017-02-11 20:35:02 +00:00
return cmd.outType(t.(lang.Tuple)[1])
}
func (mod *Module) build(t lang.Term) (llvm.Value, error) {
cmd, err := mod.matchingBuildCmd(t)
if err != nil {
return llvm.Value{}, err
}
2017-02-11 20:35:02 +00:00
return cmd.build(t.(lang.Tuple)[1])
}
2017-02-11 20:35:02 +00:00
func (mod *Module) buildFn(tt ...lang.Term) (llvm.Value, error) {
if len(tt) == 0 {
return llvm.Value{}, errors.New("function cannot be empty")
}
2017-02-11 20:35:02 +00:00
inType, err := mod.inType(tt[0])
if err != nil {
return llvm.Value{}, err
}
var inTypes []llvm.Type
if inType.TypeKind() == llvm.VoidTypeKind {
inTypes = []llvm.Type{}
} else {
inTypes = []llvm.Type{inType}
}
2017-02-11 20:35:02 +00:00
outType, err := mod.outType(tt[len(tt)-1])
if err != nil {
return llvm.Value{}, err
}
2017-02-11 20:35:02 +00:00
fn := llvm.AddFunction(mod.m, "", llvm.FunctionType(outType, inTypes, false))
block := llvm.AddBasicBlock(fn, "")
mod.b.SetInsertPoint(block, block.FirstInstruction())
var out llvm.Value
for _, t := range tt {
if out, err = mod.build(t); err != nil {
return llvm.Value{}, err
}
}
2017-02-11 20:35:02 +00:00
mod.b.CreateRet(out)
return fn, nil
}
// Dump dumps the Module's IR to stdout
func (mod *Module) Dump() {
mod.m.Dump()
}
// Run executes the Module
// TODO input and output?
func (mod *Module) Run() (interface{}, error) {
engine, err := llvm.NewExecutionEngine(mod.m)
if err != nil {
return nil, err
}
defer engine.Dispose()
funcResult := engine.RunFunction(mod.mainFn, []llvm.GenericValue{})
defer funcResult.Dispose()
return funcResult.Int(false), nil
}