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" "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 // Term is a unit of language which carries some meaning. Some Terms are
// actually comprised of multiple sub-Terms. // actually comprised of multiple sub-Terms.
type Term interface { type Term interface {
@ -33,7 +46,7 @@ func (a Atom) String() string {
// Type implements the method for Term // Type implements the method for Term
func (a Atom) Type() 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 // Type implements the method for Term
func (a Const) Type() Term { func (a Const) Type() Term {
return Const("const") return AConst
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -71,7 +84,7 @@ func (t Tuple) Type() Term {
for i := range t { for i := range t {
tt[i] = t[i].Type() 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 // Type implements the method for Term
func (l list) Type() 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() { 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("1")},
lang.Tuple{vm.Int, lang.Const("2")}, lang.Tuple{vm.Int, lang.Const("2")},
}} }}}
mod, err := vm.Build(t) mod, err := vm.Build(t)
if err != nil { if err != nil {

View File

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

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" "llvm.org/llvm/bindings/go/llvm"
) )
type val struct { // Types supported by the vm in addition to those which are part of lang
typ lang.Term var (
v llvm.Value 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, // 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 // or compiled. A Module should be Dispose()'d of once it's no longer being
// used. // used.
type Module struct { type Module struct {
b llvm.Builder b llvm.Builder
m llvm.Module m llvm.Module
mainFn llvm.Value mainFn llvm.Value
buildCmds []buildCmd
} }
var initOnce sync.Once var initOnce sync.Once
@ -38,7 +44,6 @@ func Build(t lang.Term) (*Module, error) {
b: llvm.NewBuilder(), b: llvm.NewBuilder(),
m: llvm.NewModule(""), m: llvm.NewModule(""),
} }
mod.buildCmds = buildCmds(mod)
var err error var err error
if mod.mainFn, err = mod.buildFn(t); err != nil { if mod.mainFn, err = mod.buildFn(t); err != nil {
@ -61,73 +66,39 @@ func (mod *Module) Dispose() {
//mod.b.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 // TODO make this return a val once we get function types
func (mod *Module) buildFn(tt ...lang.Term) (llvm.Value, error) { func (mod *Module) buildFn(tt ...lang.Term) (llvm.Value, error) {
if len(tt) == 0 { if len(tt) == 0 {
return llvm.Value{}, errors.New("function cannot be empty") return llvm.Value{}, errors.New("function cannot be empty")
} }
inType, err := mod.inType(tt[0]) cmds := make([]cmd, len(tt))
if err != nil { var err error
return llvm.Value{}, err for i := range tt {
} if cmds[i], err = matchCmd(tt[i]); err != nil {
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 {
return llvm.Value{}, err 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 return fn, nil
} }