refactor to use an interface instead of buildCmd

This commit is contained in:
Brian Picciano 2017-02-12 11:32:44 -07:00
parent 4180e6b072
commit 73d81dcbcc
6 changed files with 202 additions and 242 deletions

View File

@ -1,17 +0,0 @@
package lang
// Commonly used Terms
var (
// Language structure types
AAtom = Atom("atom")
AConst = Atom("const")
ATuple = Atom("tup")
AList = Atom("list")
// Match shortcuts
AUnder = Atom("_")
TDblUnder = Tuple{AUnder, AUnder}
// VM commands
AAdd = Atom("add")
)

View File

@ -6,6 +6,19 @@ import (
"strings"
)
// Commonly used Terms
var (
// Language structure types
AAtom = Atom("atom")
AConst = Atom("const")
ATuple = Atom("tup")
AList = Atom("list")
// Match shortcuts
AUnder = Atom("_")
TDblUnder = Tuple{AUnder, AUnder}
)
// Term is a unit of language which carries some meaning. Some Terms are
// actually comprised of multiple sub-Terms.
type Term interface {
@ -33,7 +46,7 @@ func (a Atom) String() string {
// Type implements the method for Term
func (a Atom) Type() Term {
return Atom("atom")
return AAtom
}
////////////////////////////////////////////////////////////////////////////////
@ -47,7 +60,7 @@ func (a Const) String() string {
// Type implements the method for Term
func (a Const) Type() Term {
return Const("const")
return AConst
}
////////////////////////////////////////////////////////////////////////////////
@ -71,7 +84,7 @@ func (t Tuple) Type() Term {
for i := range t {
tt[i] = t[i].Type()
}
return Tuple{Atom("tup"), tt}
return Tuple{ATuple, tt}
}
////////////////////////////////////////////////////////////////////////////////
@ -101,5 +114,5 @@ func (l list) String() string {
// Type implements the method for Term
func (l list) Type() Term {
return Tuple{Atom("list"), l.typ}
return Tuple{AList, l.typ}
}

View File

@ -8,10 +8,10 @@ import (
)
func main() {
t := lang.Tuple{lang.AAdd, lang.Tuple{
t := lang.Tuple{vm.Add, lang.Tuple{vm.Tuple, lang.Tuple{
lang.Tuple{vm.Int, lang.Const("1")},
lang.Tuple{vm.Int, lang.Const("2")},
}}
}}}
mod, err := vm.Build(t)
if err != nil {

View File

@ -1,42 +1,119 @@
package vm
import (
"errors"
"fmt"
"strconv"
"github.com/mediocregopher/ginger/lang"
"llvm.org/llvm/bindings/go/llvm"
)
type buildCmd struct {
pattern lang.Tuple
inTypeFn func(lang.Term) (llvm.Type, error)
outTypeFn func(lang.Term) (llvm.Type, error)
buildFn func(lang.Term) (val, error)
type cmd interface {
inType() valType
outType() valType
build(*Module) (llvm.Value, error)
}
func (cmd buildCmd) matches(t lang.Term) bool {
return lang.Match(cmd.pattern, t)
type valType struct {
term lang.Term
llvm llvm.Type
}
func (cmd buildCmd) inType(t lang.Term) (llvm.Type, error) {
if cmd.inTypeFn == nil {
return llvm.VoidType(), nil
// most types don't have an input, so we use this as a shortcut
type voidIn struct{}
func (voidIn) inType() valType {
return valType{
term: lang.Tuple{},
llvm: llvm.VoidType(),
}
return cmd.inTypeFn(t)
}
func (cmd buildCmd) outType(t lang.Term) (llvm.Type, error) {
if cmd.outTypeFn == nil {
return llvm.VoidType(), nil
////////////////////////////////////////////////////////////////////////////////
type intCmd struct {
voidIn
c lang.Const
}
func (ic intCmd) outType() valType {
return valType{
term: Int,
llvm: llvm.Int64Type(),
}
return cmd.outTypeFn(t)
}
func (cmd buildCmd) build(t lang.Term) (val, error) {
return cmd.buildFn(t)
func (ic intCmd) build(mod *Module) (llvm.Value, error) {
ci, err := strconv.ParseInt(string(ic.c), 10, 64)
if err != nil {
return llvm.Value{}, err
}
return llvm.ConstInt(llvm.Int64Type(), uint64(ci), false), nil
}
func buildCmds(mod *Module) []buildCmd {
////////////////////////////////////////////////////////////////////////////////
type tupCmd struct {
voidIn
els []cmd
}
func (tc tupCmd) outType() valType {
termTypes := make(lang.Tuple, len(tc.els))
llvmTypes := make([]llvm.Type, len(tc.els))
for i := range tc.els {
elValType := tc.els[i].outType()
termTypes[i] = elValType.term
llvmTypes[i] = elValType.llvm
}
vt := valType{term: lang.Tuple{Tuple, termTypes}}
if len(llvmTypes) == 0 {
vt.llvm = llvm.VoidType()
} else {
vt.llvm = llvm.StructType(llvmTypes, false)
}
return vt
}
func (tc tupCmd) build(mod *Module) (llvm.Value, error) {
str := llvm.Undef(tc.outType().llvm)
var val llvm.Value
var err error
for i := range tc.els {
if val, err = tc.els[i].build(mod); err != nil {
str = mod.b.CreateInsertValue(str, val, i, "")
}
}
return str, err
}
////////////////////////////////////////////////////////////////////////////////
type addCmd struct {
voidIn
a, b intCmd
}
func (ac addCmd) outType() valType {
return ac.a.outType()
}
func (ac addCmd) build(mod *Module) (llvm.Value, error) {
av, err := ac.a.build(mod)
if err != nil {
return llvm.Value{}, err
}
bv, err := ac.b.build(mod)
if err != nil {
return llvm.Value{}, err
}
return mod.b.CreateAdd(av, bv, ""), nil
}
////////////////////////////////////////////////////////////////////////////////
func matchCmd(t lang.Term) (cmd, error) {
aPat := func(a lang.Atom) lang.Tuple {
return lang.Tuple{lang.AAtom, a}
}
@ -46,98 +123,58 @@ func buildCmds(mod *Module) []buildCmd {
tPat := func(el ...lang.Term) lang.Tuple {
return lang.Tuple{Tuple, lang.Tuple(el)}
}
buildPat := func(a lang.Atom, b lang.Tuple) lang.Tuple {
return tPat(aPat(a), b)
if !lang.Match(tPat(aPat(lang.AUnder), lang.TDblUnder), t) {
return nil, fmt.Errorf("term %v does not look like a vm command", t)
}
return []buildCmd{
{ // (int 42)
pattern: buildPat(Int, cPat(lang.AUnder)),
outTypeFn: func(t lang.Term) (llvm.Type, error) {
return llvm.Int64Type(), nil
},
buildFn: func(t lang.Term) (val, error) {
con := t.(lang.Const)
coni, err := strconv.ParseInt(string(con), 10, 64)
if err != nil {
return val{}, err
}
return val{
// TODO why does this have to be cast?
v: llvm.ConstInt(llvm.Int64Type(), uint64(coni), false),
typ: Int,
}, nil
},
},
k := t.(lang.Tuple)[0].(lang.Atom)
v := t.(lang.Tuple)[1]
{ // (tup ((atom foo) (const 10)))
pattern: buildPat(Tuple, lang.Tuple{Tuple, lang.AUnder}),
outTypeFn: func(t lang.Term) (llvm.Type, error) {
tup := t.(lang.Tuple)
if len(tup) == 0 {
return llvm.VoidType(), nil
}
var err error
typs := make([]llvm.Type, len(tup))
for i := range tup {
if typs[i], err = mod.outType(tup[i]); err != nil {
return llvm.Type{}, err
}
}
return llvm.StructType(typs, false), nil
},
buildFn: func(t lang.Term) (val, error) {
tup := t.(lang.Tuple)
// if the tuple is empty then it is a void
if len(tup) == 0 {
return val{
v: llvm.Undef(llvm.VoidType()),
typ: lang.Tuple{Tuple, lang.Tuple{}},
}, nil
}
// for when v is a Tuple argument, convenience function for casting
vAsTup := func(n int) ([]cmd, error) {
vcmd, err := matchCmd(v)
if err != nil {
return nil, err
}
vtup, ok := vcmd.(tupCmd)
if !ok || len(vtup.els) != n {
return nil, fmt.Errorf("cmd %v expects a %d-tuple argument", k, n)
}
return vtup.els, nil
}
var err error
vals := make([]val, len(tup))
typs := make([]llvm.Type, len(tup))
ttyps := make([]lang.Term, len(tup))
for i := range tup {
if vals[i], err = mod.build(tup[i]); err != nil {
return val{}, err
}
typs[i] = vals[i].v.Type()
ttyps[i] = vals[i].typ
}
str := llvm.Undef(llvm.StructType(typs, false))
for i := range vals {
str = mod.b.CreateInsertValue(str, vals[i].v, i, "")
}
return val{
v: str,
typ: lang.Tuple{Tuple, lang.Tuple(ttyps)},
}, nil
},
},
{ // (add ((const 5) (var foo)))
pattern: buildPat(lang.AAdd, tPat(lang.TDblUnder, lang.TDblUnder)),
outTypeFn: func(t lang.Term) (llvm.Type, error) {
return llvm.Int64Type(), nil
},
buildFn: func(t lang.Term) (val, error) {
tup := t.(lang.Tuple)
v1, err := mod.build(tup[0])
if err != nil {
return val{}, err
}
v2, err := mod.build(tup[1])
if err != nil {
return val{}, err
}
return val{
v: mod.b.CreateAdd(v1.v, v2.v, ""),
typ: v1.typ,
}, nil
},
},
switch k {
case Int:
if !lang.Match(cPat(lang.AUnder), v) {
return nil, errors.New("int requires constant arg")
}
return intCmd{c: v.(lang.Const)}, nil
case Tuple:
if !lang.Match(lang.Tuple{Tuple, lang.AUnder}, v) {
return nil, errors.New("tup requires tuple arg")
}
tup := v.(lang.Tuple)
tc := tupCmd{els: make([]cmd, len(tup))}
var err error
for i := range tup {
if tc.els[i], err = matchCmd(tup[i]); err != nil {
return nil, err
}
}
return tc, nil
case Add:
els, err := vAsTup(2)
if err != nil {
return nil, err
} else if _, ok := els[0].(intCmd); !ok {
return nil, errors.New("add args must be numbers")
} else if _, ok := els[1].(intCmd); !ok {
return nil, errors.New("add args must be numbers")
} else if !lang.Equal(els[0].outType().term, els[1].outType().term) {
return nil, errors.New("add args must be the same type")
}
return addCmd{a: els[0].(intCmd), b: els[1].(intCmd)}, nil
default:
return nil, fmt.Errorf("cmd %v unknown, or its args are malformed", t)
}
}

View File

@ -1,44 +0,0 @@
package vm
import (
"fmt"
"github.com/mediocregopher/ginger/lang"
"llvm.org/llvm/bindings/go/llvm"
)
// Types supported by the vm in addition to those which are part of lang
var (
Atom = lang.AAtom
Tuple = lang.ATuple
Int = lang.Atom("int")
)
var (
tupPat = lang.Tuple{lang.ATuple, lang.Tuple{
lang.Tuple{lang.AAtom, Tuple},
lang.Tuple{lang.ATuple, lang.AUnder},
}}
)
func termToType(t lang.Term) (llvm.Type, error) {
switch {
case lang.Equal(t, Int):
return llvm.Int64Type(), nil
case lang.Match(tupPat, t):
tup := t.(lang.Tuple)[1].(lang.Tuple)
if len(tup) == 0 {
return llvm.VoidType(), nil
}
var err error
typs := make([]llvm.Type, len(tup))
for i := range tup {
if typs[i], err = termToType(tup[i]); err != nil {
return llvm.Type{}, err
}
}
return llvm.StructType(typs, false), nil
default:
return llvm.Type{}, fmt.Errorf("type %v not supported", t)
}
}

105
vm/vm.go
View File

@ -10,19 +10,25 @@ import (
"llvm.org/llvm/bindings/go/llvm"
)
type val struct {
typ lang.Term
v llvm.Value
}
// Types supported by the vm in addition to those which are part of lang
var (
Atom = lang.AAtom
Tuple = lang.ATuple
Int = lang.Atom("int")
)
// Commands supported by the vm. Each of the types are also commands
var (
Add = lang.Atom("add")
)
// 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 {
b llvm.Builder
m llvm.Module
mainFn llvm.Value
buildCmds []buildCmd
b llvm.Builder
m llvm.Module
mainFn llvm.Value
}
var initOnce sync.Once
@ -38,7 +44,6 @@ func Build(t lang.Term) (*Module, error) {
b: llvm.NewBuilder(),
m: llvm.NewModule(""),
}
mod.buildCmds = buildCmds(mod)
var err error
if mod.mainFn, err = mod.buildFn(t); err != nil {
@ -61,73 +66,39 @@ func (mod *Module) Dispose() {
//mod.b.Dispose()
}
func (mod *Module) matchingBuildCmd(t lang.Term) (buildCmd, error) {
for _, cmd := range mod.buildCmds {
if !cmd.matches(t) {
continue
}
return cmd, nil
}
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
}
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
}
return cmd.outType(t.(lang.Tuple)[1])
}
func (mod *Module) build(t lang.Term) (val, error) {
cmd, err := mod.matchingBuildCmd(t)
if err != nil {
return val{}, err
}
return cmd.build(t.(lang.Tuple)[1])
}
// TODO make this return a val once we get function types
func (mod *Module) buildFn(tt ...lang.Term) (llvm.Value, error) {
if len(tt) == 0 {
return llvm.Value{}, errors.New("function cannot be empty")
}
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}
}
outType, err := mod.outType(tt[len(tt)-1])
if err != nil {
return llvm.Value{}, err
}
fn := llvm.AddFunction(mod.m, "", llvm.FunctionType(outType, inTypes, false))
block := llvm.AddBasicBlock(fn, "")
mod.b.SetInsertPoint(block, block.FirstInstruction())
var out val
for _, t := range tt {
if out, err = mod.build(t); err != nil {
cmds := make([]cmd, len(tt))
var err error
for i := range tt {
if cmds[i], err = matchCmd(tt[i]); err != nil {
return llvm.Value{}, err
}
}
mod.b.CreateRet(out.v)
var llvmIns []llvm.Type
if in := cmds[0].inType(); in.llvm.TypeKind() == llvm.VoidTypeKind {
llvmIns = []llvm.Type{}
} else {
llvmIns = []llvm.Type{in.llvm}
}
llvmOut := cmds[len(cmds)-1].outType().llvm
fn := llvm.AddFunction(mod.m, "", llvm.FunctionType(llvmOut, llvmIns, false))
block := llvm.AddBasicBlock(fn, "")
mod.b.SetInsertPoint(block, block.FirstInstruction())
var out llvm.Value
for i := range cmds {
if out, err = cmds[i].build(mod); err != nil {
return llvm.Value{}, nil
}
}
mod.b.CreateRet(out)
return fn, nil
}