From 73d81dcbccda42057cacfec568728a26ea873525 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Sun, 12 Feb 2017 11:32:44 -0700 Subject: [PATCH] refactor to use an interface instead of buildCmd --- lang/common.go | 17 ---- lang/lang.go | 21 +++- main.go | 4 +- vm/cmds.go | 253 ++++++++++++++++++++++++++++--------------------- vm/types.go | 44 --------- vm/vm.go | 105 ++++++++------------ 6 files changed, 202 insertions(+), 242 deletions(-) delete mode 100644 lang/common.go delete mode 100644 vm/types.go diff --git a/lang/common.go b/lang/common.go deleted file mode 100644 index 30a6d4f..0000000 --- a/lang/common.go +++ /dev/null @@ -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") -) diff --git a/lang/lang.go b/lang/lang.go index bf03ce7..c539351 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -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} } diff --git a/main.go b/main.go index 16e3e02..c839f3b 100644 --- a/main.go +++ b/main.go @@ -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 { diff --git a/vm/cmds.go b/vm/cmds.go index 9807fa5..518c9c2 100644 --- a/vm/cmds.go +++ b/vm/cmds.go @@ -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) } } diff --git a/vm/types.go b/vm/types.go deleted file mode 100644 index 4b2d8e0..0000000 --- a/vm/types.go +++ /dev/null @@ -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) - } -} diff --git a/vm/vm.go b/vm/vm.go index ffb49ea..336d315 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -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 }