forked from TrueCloudLab/neoneo-go
473 lines
10 KiB
Go
473 lines
10 KiB
Go
|
package compiler
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/binary"
|
||
|
"go/ast"
|
||
|
"go/constant"
|
||
|
"go/token"
|
||
|
"go/types"
|
||
|
"log"
|
||
|
|
||
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||
|
)
|
||
|
|
||
|
const mainIdent = "Main"
|
||
|
|
||
|
type codegen struct {
|
||
|
prog *bytes.Buffer
|
||
|
|
||
|
// Type information
|
||
|
typeInfo *types.Info
|
||
|
|
||
|
// a mapping of func identifiers with its scope.
|
||
|
funcs map[string]*funcScope
|
||
|
|
||
|
// current function being generated
|
||
|
fctx *funcScope
|
||
|
|
||
|
// Label table for recording jump destinations.
|
||
|
l []int
|
||
|
}
|
||
|
|
||
|
// newLabel creates a new label to jump to
|
||
|
func (c *codegen) newLabel() (l int) {
|
||
|
l = len(c.l)
|
||
|
c.l = append(c.l, -1)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *codegen) setLabel(l int) {
|
||
|
c.l[l] = c.pc() + 1
|
||
|
}
|
||
|
|
||
|
// pc return the program offset off the last instruction.
|
||
|
func (c *codegen) pc() int {
|
||
|
return c.prog.Len() - 1
|
||
|
}
|
||
|
|
||
|
func (c *codegen) emitLoadConst(t types.TypeAndValue) {
|
||
|
switch typ := t.Type.(type) {
|
||
|
case *types.Basic:
|
||
|
switch typ.Kind() {
|
||
|
case types.Int:
|
||
|
val, _ := constant.Int64Val(t.Value)
|
||
|
emitInt(c.prog, val)
|
||
|
case types.String:
|
||
|
val := constant.StringVal(t.Value)
|
||
|
emitString(c.prog, val)
|
||
|
case types.Bool, types.UntypedBool:
|
||
|
val := constant.BoolVal(t.Value)
|
||
|
emitBool(c.prog, val)
|
||
|
}
|
||
|
default:
|
||
|
log.Fatalf("compiler don't know how to convert this constant: %v", t)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *codegen) emitLoadLocal(name string) {
|
||
|
pos := c.fctx.loadLocal(name)
|
||
|
if pos < 0 {
|
||
|
log.Fatalf("cannot load local variable with position: %d", pos)
|
||
|
}
|
||
|
|
||
|
emitOpcode(c.prog, vm.Ofromaltstack)
|
||
|
emitOpcode(c.prog, vm.Odup)
|
||
|
emitOpcode(c.prog, vm.Otoaltstack)
|
||
|
|
||
|
emitInt(c.prog, int64(pos))
|
||
|
emitOpcode(c.prog, vm.Opickitem)
|
||
|
}
|
||
|
|
||
|
func (c *codegen) emitLoadStructField(sName, fName string) {
|
||
|
strct := c.fctx.loadStruct(sName)
|
||
|
pos := strct.loadField(fName)
|
||
|
emitInt(c.prog, int64(pos))
|
||
|
emitOpcode(c.prog, vm.Opickitem)
|
||
|
}
|
||
|
|
||
|
func (c *codegen) emitStoreLocal(pos int) {
|
||
|
emitOpcode(c.prog, vm.Ofromaltstack)
|
||
|
emitOpcode(c.prog, vm.Odup)
|
||
|
emitOpcode(c.prog, vm.Otoaltstack)
|
||
|
|
||
|
if pos < 0 {
|
||
|
log.Fatalf("invalid position to store local: %d", pos)
|
||
|
}
|
||
|
|
||
|
emitInt(c.prog, int64(pos))
|
||
|
emitInt(c.prog, 2)
|
||
|
emitOpcode(c.prog, vm.Oroll)
|
||
|
emitOpcode(c.prog, vm.Osetitem)
|
||
|
}
|
||
|
|
||
|
func (c *codegen) emitStoreStructField(sName, fName string) {
|
||
|
strct := c.fctx.loadStruct(sName)
|
||
|
pos := strct.loadField(fName)
|
||
|
emitInt(c.prog, int64(pos))
|
||
|
emitOpcode(c.prog, vm.Orot)
|
||
|
emitOpcode(c.prog, vm.Osetitem)
|
||
|
}
|
||
|
|
||
|
func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) {
|
||
|
var (
|
||
|
f *funcScope
|
||
|
ok bool
|
||
|
)
|
||
|
|
||
|
f, ok = c.funcs[decl.Name.Name]
|
||
|
if ok {
|
||
|
c.setLabel(f.label)
|
||
|
} else {
|
||
|
f = c.newFunc(decl)
|
||
|
}
|
||
|
c.fctx = f
|
||
|
|
||
|
emitInt(c.prog, f.stackSize())
|
||
|
emitOpcode(c.prog, vm.Onewarray)
|
||
|
emitOpcode(c.prog, vm.Otoaltstack)
|
||
|
|
||
|
// We need to handle methods, which in Go, is just syntactic sugar.
|
||
|
// The method receiver will be passed in as first argument.
|
||
|
// We check if this declaration has a receiver and load it into scope.
|
||
|
//
|
||
|
// FIXME: For now we will hard cast this to a struct. We can later finetune this
|
||
|
// to support other types.
|
||
|
if decl.Recv != nil {
|
||
|
for _, arg := range decl.Recv.List {
|
||
|
strct := c.fctx.newStruct()
|
||
|
|
||
|
ident := arg.Names[0]
|
||
|
strct.initializeFields(ident, c.typeInfo)
|
||
|
l := c.fctx.newLocal(ident.Name)
|
||
|
c.emitStoreLocal(l)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Load the arguments in scope.
|
||
|
for _, arg := range decl.Type.Params.List {
|
||
|
name := arg.Names[0].Name // for now.
|
||
|
l := c.fctx.newLocal(name)
|
||
|
c.emitStoreLocal(l)
|
||
|
}
|
||
|
|
||
|
ast.Walk(c, decl.Body)
|
||
|
}
|
||
|
|
||
|
func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||
|
switch n := node.(type) {
|
||
|
|
||
|
case *ast.AssignStmt:
|
||
|
for i := 0; i < len(n.Lhs); i++ {
|
||
|
// resolve the whole right hand side.
|
||
|
ast.Walk(c, n.Rhs[i])
|
||
|
// check if we are assigning to a struct or an identifier
|
||
|
switch t := n.Lhs[i].(type) {
|
||
|
case *ast.Ident:
|
||
|
l := c.fctx.loadLocal(t.Name)
|
||
|
c.emitStoreLocal(l)
|
||
|
|
||
|
case *ast.SelectorExpr:
|
||
|
switch n := t.X.(type) {
|
||
|
case *ast.Ident:
|
||
|
c.emitLoadLocal(n.Name) // load the struct
|
||
|
c.emitStoreStructField(n.Name, t.Sel.Name) // store the field
|
||
|
default:
|
||
|
log.Fatal("nested selector assigns not supported yet")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
|
||
|
case *ast.ReturnStmt:
|
||
|
if len(n.Results) > 1 {
|
||
|
log.Fatal("multiple returns not supported.")
|
||
|
}
|
||
|
|
||
|
emitOpcode(c.prog, vm.Ojmp)
|
||
|
emitOpcode(c.prog, vm.Opcode(0x03))
|
||
|
emitOpcode(c.prog, vm.Opush0)
|
||
|
|
||
|
if len(n.Results) > 0 {
|
||
|
ast.Walk(c, n.Results[0])
|
||
|
}
|
||
|
|
||
|
emitOpcode(c.prog, vm.Onop)
|
||
|
emitOpcode(c.prog, vm.Ofromaltstack)
|
||
|
emitOpcode(c.prog, vm.Odrop)
|
||
|
emitOpcode(c.prog, vm.Oret)
|
||
|
return nil
|
||
|
|
||
|
case *ast.IfStmt:
|
||
|
lIf := c.newLabel()
|
||
|
lElse := c.newLabel()
|
||
|
if n.Cond != nil {
|
||
|
ast.Walk(c, n.Cond)
|
||
|
emitJmp(c.prog, vm.Ojmpifnot, int16(lElse))
|
||
|
}
|
||
|
|
||
|
c.setLabel(lIf)
|
||
|
ast.Walk(c, n.Body)
|
||
|
|
||
|
if n.Else != nil {
|
||
|
// TODO: handle else statements.
|
||
|
// emitJmp(c.prog, vm.Ojmp, int16(lEnd))
|
||
|
}
|
||
|
c.setLabel(lElse)
|
||
|
if n.Else != nil {
|
||
|
ast.Walk(c, n.Else)
|
||
|
}
|
||
|
return nil
|
||
|
|
||
|
case *ast.BasicLit:
|
||
|
c.emitLoadConst(c.getTypeInfo(n))
|
||
|
return nil
|
||
|
|
||
|
case *ast.Ident:
|
||
|
if isIdentBool(n) {
|
||
|
c.emitLoadConst(makeBoolFromIdent(n, c.typeInfo))
|
||
|
} else {
|
||
|
c.emitLoadLocal(n.Name)
|
||
|
}
|
||
|
return nil
|
||
|
|
||
|
case *ast.CompositeLit:
|
||
|
switch t := n.Type.(type) {
|
||
|
case *ast.Ident:
|
||
|
typ := c.typeInfo.ObjectOf(t).Type().Underlying()
|
||
|
switch typ.(type) {
|
||
|
case *types.Struct:
|
||
|
c.convertStruct(n)
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
ln := len(n.Elts)
|
||
|
for i := ln - 1; i >= 0; i-- {
|
||
|
c.emitLoadConst(c.getTypeInfo(n.Elts[i]))
|
||
|
}
|
||
|
emitInt(c.prog, int64(ln))
|
||
|
emitOpcode(c.prog, vm.Opack)
|
||
|
}
|
||
|
return nil
|
||
|
|
||
|
case *ast.BinaryExpr:
|
||
|
switch n.Op {
|
||
|
case token.LAND:
|
||
|
ast.Walk(c, n.X)
|
||
|
emitJmp(c.prog, vm.Ojmpifnot, int16(len(c.l)-1))
|
||
|
ast.Walk(c, n.Y)
|
||
|
return nil
|
||
|
|
||
|
case token.LOR:
|
||
|
ast.Walk(c, n.X)
|
||
|
emitJmp(c.prog, vm.Ojmpif, int16(len(c.l)-2))
|
||
|
ast.Walk(c, n.Y)
|
||
|
return nil
|
||
|
|
||
|
default:
|
||
|
// The AST package will try to resolve all basic literals for us.
|
||
|
// If the typeinfo.Value is not nil we know that the expr is resolved
|
||
|
// and needs no further action. e.g. x := 2 + 2 + 2 will be resolved to 6.
|
||
|
if tinfo := c.getTypeInfo(n); tinfo.Value != nil {
|
||
|
c.emitLoadConst(tinfo)
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
ast.Walk(c, n.X)
|
||
|
ast.Walk(c, n.Y)
|
||
|
|
||
|
switch n.Op {
|
||
|
case token.ADD:
|
||
|
emitOpcode(c.prog, vm.Oadd)
|
||
|
case token.SUB:
|
||
|
emitOpcode(c.prog, vm.Osub)
|
||
|
case token.MUL:
|
||
|
emitOpcode(c.prog, vm.Omul)
|
||
|
case token.QUO:
|
||
|
emitOpcode(c.prog, vm.Odiv)
|
||
|
case token.LSS:
|
||
|
emitOpcode(c.prog, vm.Olt)
|
||
|
case token.LEQ:
|
||
|
emitOpcode(c.prog, vm.Olte)
|
||
|
case token.GTR:
|
||
|
emitOpcode(c.prog, vm.Ogt)
|
||
|
case token.GEQ:
|
||
|
emitOpcode(c.prog, vm.Ogte)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
case *ast.CallExpr:
|
||
|
var (
|
||
|
f *funcScope
|
||
|
ok bool
|
||
|
numArgs = len(n.Args)
|
||
|
)
|
||
|
|
||
|
switch fun := n.Fun.(type) {
|
||
|
case *ast.Ident:
|
||
|
f, ok = c.funcs[fun.Name]
|
||
|
if !ok {
|
||
|
log.Fatalf("could not resolve function %s", fun.Name)
|
||
|
}
|
||
|
case *ast.SelectorExpr:
|
||
|
ast.Walk(c, fun.X)
|
||
|
f, ok = c.funcs[fun.Sel.Name]
|
||
|
if !ok {
|
||
|
log.Fatalf("could not resolve function %s", fun.Sel.Name)
|
||
|
}
|
||
|
// Dont forget to add 1 extra argument when its a method.
|
||
|
numArgs++
|
||
|
}
|
||
|
|
||
|
for _, arg := range n.Args {
|
||
|
ast.Walk(c, arg)
|
||
|
}
|
||
|
if numArgs == 2 {
|
||
|
emitOpcode(c.prog, vm.Oswap)
|
||
|
}
|
||
|
if numArgs == 3 {
|
||
|
emitInt(c.prog, 2)
|
||
|
emitOpcode(c.prog, vm.Oxswap)
|
||
|
}
|
||
|
|
||
|
// c# compiler adds a NOP (0x61) before every function call. Dont think its relevant
|
||
|
// and we could easily removed it, but to be consistent with the original compiler I
|
||
|
// will put them in. ^^
|
||
|
emitOpcode(c.prog, vm.Onop)
|
||
|
emitCall(c.prog, vm.Ocall, int16(f.label))
|
||
|
return nil
|
||
|
|
||
|
case *ast.SelectorExpr:
|
||
|
switch t := n.X.(type) {
|
||
|
case *ast.Ident:
|
||
|
c.emitLoadLocal(t.Name) // load the struct
|
||
|
c.emitLoadStructField(t.Name, n.Sel.Name) // load the field
|
||
|
default:
|
||
|
log.Fatal("nested selectors not supported yet")
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c *codegen) convertStruct(lit *ast.CompositeLit) {
|
||
|
emitOpcode(c.prog, vm.Onop)
|
||
|
emitInt(c.prog, int64(len(lit.Elts)))
|
||
|
emitOpcode(c.prog, vm.Onewstruct)
|
||
|
emitOpcode(c.prog, vm.Otoaltstack)
|
||
|
|
||
|
// Create a new struct scope to store the positions of its variables.
|
||
|
strct := c.fctx.newStruct()
|
||
|
|
||
|
for _, field := range lit.Elts {
|
||
|
f := field.(*ast.KeyValueExpr)
|
||
|
// Walk to resolve the expression of the value.
|
||
|
ast.Walk(c, f.Value)
|
||
|
l := strct.newField(f.Key.(*ast.Ident).Name)
|
||
|
c.emitStoreLocal(l)
|
||
|
}
|
||
|
emitOpcode(c.prog, vm.Ofromaltstack)
|
||
|
}
|
||
|
|
||
|
func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope {
|
||
|
f := newFuncScope(decl, c.newLabel())
|
||
|
c.funcs[f.name] = f
|
||
|
return f
|
||
|
}
|
||
|
|
||
|
func (c *codegen) getTypeInfo(expr ast.Expr) types.TypeAndValue {
|
||
|
return c.typeInfo.Types[expr]
|
||
|
}
|
||
|
|
||
|
func makeBoolFromIdent(ident *ast.Ident, tinfo *types.Info) types.TypeAndValue {
|
||
|
var b bool
|
||
|
if ident.Name == "true" {
|
||
|
b = true
|
||
|
} else if ident.Name == "false" {
|
||
|
b = false
|
||
|
} else {
|
||
|
log.Fatalf("givent identifier cannot be converted to a boolean => %s", ident.Name)
|
||
|
}
|
||
|
|
||
|
return types.TypeAndValue{
|
||
|
Type: tinfo.ObjectOf(ident).Type(),
|
||
|
Value: constant.MakeBool(b),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func isIdentBool(ident *ast.Ident) bool {
|
||
|
return ident.Name == "true" || ident.Name == "false"
|
||
|
}
|
||
|
|
||
|
// CodeGen is the function that compiles the program to bytecode.
|
||
|
func CodeGen(f *ast.File, tInfo *types.Info) (*bytes.Buffer, error) {
|
||
|
c := &codegen{
|
||
|
prog: new(bytes.Buffer),
|
||
|
l: []int{},
|
||
|
funcs: map[string]*funcScope{},
|
||
|
typeInfo: tInfo,
|
||
|
}
|
||
|
|
||
|
var main *ast.FuncDecl
|
||
|
ast.Inspect(f, func(n ast.Node) bool {
|
||
|
switch t := n.(type) {
|
||
|
case *ast.FuncDecl:
|
||
|
if t.Name.Name == mainIdent {
|
||
|
main = t
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
return true
|
||
|
})
|
||
|
if main == nil {
|
||
|
log.Fatal("could not find func main. did you forgot to declare it?")
|
||
|
}
|
||
|
|
||
|
c.resolveFuncDecls(f)
|
||
|
c.convertFuncDecl(main)
|
||
|
|
||
|
for _, decl := range f.Decls {
|
||
|
switch n := decl.(type) {
|
||
|
case *ast.FuncDecl:
|
||
|
if n.Name.Name != mainIdent {
|
||
|
c.convertFuncDecl(n)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
c.writeJumps()
|
||
|
|
||
|
return c.prog, nil
|
||
|
}
|
||
|
|
||
|
func (c *codegen) resolveFuncDecls(f *ast.File) {
|
||
|
for _, decl := range f.Decls {
|
||
|
switch n := decl.(type) {
|
||
|
case *ast.FuncDecl:
|
||
|
if n.Name.Name != mainIdent {
|
||
|
c.newFunc(n)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *codegen) writeJumps() {
|
||
|
b := c.prog.Bytes()
|
||
|
for i, op := range b {
|
||
|
j := i + 1
|
||
|
switch vm.Opcode(op) {
|
||
|
case vm.Ojmpifnot, vm.Ojmpif, vm.Ocall:
|
||
|
index := binary.LittleEndian.Uint16(b[j : j+2])
|
||
|
if int(index) > len(c.l) {
|
||
|
continue
|
||
|
}
|
||
|
offset := uint16(c.l[index] - i)
|
||
|
if offset < 0 {
|
||
|
log.Fatalf("new offset is negative, table list %v", c.l)
|
||
|
}
|
||
|
binary.LittleEndian.PutUint16(b[j:j+2], offset)
|
||
|
}
|
||
|
}
|
||
|
}
|