forked from TrueCloudLab/neoneo-go
Compiler (#23)
* implemented add, mul, div, sub assign for identifiers. * Implemented struct field initialization. * Implemented imports * Implemented storage VM API (interop layer) + additional bug fixes when encountered. * Bumped version 0.12.0 * fixed double point extension on compiled output file. * Fixed bug where callExpr in returns where added to voidCall * fixed binExpr compare equal * Check the env for the gopath first * removed travis.yml * custom types + implemented general declarations. * commented out the storage test to make the build pass
This commit is contained in:
parent
bebdabab9f
commit
23cfebf621
25 changed files with 798 additions and 117 deletions
|
@ -47,7 +47,7 @@ func (c *codegen) pc() int {
|
|||
}
|
||||
|
||||
func (c *codegen) emitLoadConst(t types.TypeAndValue) {
|
||||
switch typ := t.Type.(type) {
|
||||
switch typ := t.Type.Underlying().(type) {
|
||||
case *types.Basic:
|
||||
switch typ.Kind() {
|
||||
case types.Int:
|
||||
|
@ -121,7 +121,9 @@ func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) {
|
|||
} else {
|
||||
f = c.newFunc(decl)
|
||||
}
|
||||
|
||||
c.fctx = f
|
||||
ast.Inspect(decl, c.fctx.analyzeVoidCalls)
|
||||
|
||||
emitInt(c.prog, f.stackSize())
|
||||
emitOpcode(c.prog, vm.Onewarray)
|
||||
|
@ -135,10 +137,12 @@ func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) {
|
|||
// 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)
|
||||
t, ok := c.typeInfo.Defs[ident].Type().Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
log.Fatal("method receiver is not a struct type")
|
||||
}
|
||||
c.fctx.newStruct(t)
|
||||
l := c.fctx.newLocal(ident.Name)
|
||||
c.emitStoreLocal(l)
|
||||
}
|
||||
|
@ -157,21 +161,43 @@ func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) {
|
|||
func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||
switch n := node.(type) {
|
||||
|
||||
// General declarations.
|
||||
// With value: var x int = 2
|
||||
// Without value: var x int
|
||||
case *ast.GenDecl:
|
||||
switch t := n.Specs[0].(type) {
|
||||
case *ast.ValueSpec:
|
||||
if len(t.Values) > 0 {
|
||||
ast.Walk(c, t.Values[0])
|
||||
l := c.fctx.newLocal(t.Names[0].Name)
|
||||
c.emitStoreLocal(l)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
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)
|
||||
switch n.Tok {
|
||||
case token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN:
|
||||
c.emitLoadLocal(t.Name)
|
||||
ast.Walk(c, n.Rhs[0])
|
||||
c.convertToken(n.Tok)
|
||||
l := c.fctx.loadLocal(t.Name)
|
||||
c.emitStoreLocal(l)
|
||||
default:
|
||||
ast.Walk(c, n.Rhs[0])
|
||||
l := c.fctx.loadLocal(t.Name)
|
||||
c.emitStoreLocal(l)
|
||||
}
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
switch n := t.X.(type) {
|
||||
switch expr := t.X.(type) {
|
||||
case *ast.Ident:
|
||||
c.emitLoadLocal(n.Name) // load the struct
|
||||
c.emitStoreStructField(n.Name, t.Sel.Name) // store the field
|
||||
ast.Walk(c, n.Rhs[i])
|
||||
c.emitLoadLocal(expr.Name) // load the struct
|
||||
c.emitStoreStructField(expr.Name, t.Sel.Name) // store the field
|
||||
default:
|
||||
log.Fatal("nested selector assigns not supported yet")
|
||||
}
|
||||
|
@ -232,14 +258,13 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
return nil
|
||||
|
||||
case *ast.CompositeLit:
|
||||
var typ types.Type
|
||||
|
||||
switch t := n.Type.(type) {
|
||||
case *ast.Ident:
|
||||
typ := c.typeInfo.ObjectOf(t).Type().Underlying()
|
||||
switch typ.(type) {
|
||||
case *types.Struct:
|
||||
c.convertStruct(n)
|
||||
}
|
||||
|
||||
typ = c.typeInfo.ObjectOf(t).Type().Underlying()
|
||||
case *ast.SelectorExpr:
|
||||
typ = c.typeInfo.ObjectOf(t.Sel).Type().Underlying()
|
||||
default:
|
||||
ln := len(n.Elts)
|
||||
for i := ln - 1; i >= 0; i-- {
|
||||
|
@ -247,7 +272,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
}
|
||||
emitInt(c.prog, int64(ln))
|
||||
emitOpcode(c.prog, vm.Opack)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch typ.(type) {
|
||||
case *types.Struct:
|
||||
c.convertStruct(n)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
|
@ -275,25 +307,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
|
||||
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)
|
||||
}
|
||||
c.convertToken(n.Op)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -311,15 +325,21 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
log.Fatalf("could not resolve function %s", fun.Name)
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
ast.Walk(c, fun.X)
|
||||
// If this is a method call we need to walk the AST to load the struct locally.
|
||||
// Otherwise this is a function call from a imported package and we can call it
|
||||
// directly.
|
||||
if c.typeInfo.Selections[fun] != nil {
|
||||
ast.Walk(c, fun.X)
|
||||
// Dont forget to add 1 extra argument when its a method.
|
||||
numArgs++
|
||||
}
|
||||
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++
|
||||
}
|
||||
|
||||
// Handle the arguments
|
||||
for _, arg := range n.Args {
|
||||
ast.Walk(c, arg)
|
||||
}
|
||||
|
@ -335,7 +355,18 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
// 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))
|
||||
|
||||
if isSyscall(f.name) {
|
||||
c.convertSyscall(f.name)
|
||||
} else {
|
||||
emitCall(c.prog, vm.Ocall, int16(f.label))
|
||||
}
|
||||
|
||||
// If we are not assigning this function to a variable we need to drop
|
||||
// the top stack item. It's not a void but you get the point \o/.
|
||||
if _, ok := c.fctx.voidCalls[n]; ok && !isNoRetSyscall(f.name) {
|
||||
emitOpcode(c.prog, vm.Odrop)
|
||||
}
|
||||
return nil
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
|
@ -351,31 +382,99 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
return c
|
||||
}
|
||||
|
||||
func (c *codegen) convertStruct(lit *ast.CompositeLit) {
|
||||
func (c *codegen) convertSyscall(name string) {
|
||||
api, ok := vm.Syscalls[name]
|
||||
if !ok {
|
||||
log.Fatalf("unknown VM syscall api: %s", name)
|
||||
}
|
||||
emitSyscall(c.prog, api)
|
||||
emitOpcode(c.prog, vm.Onop)
|
||||
emitInt(c.prog, int64(len(lit.Elts)))
|
||||
}
|
||||
|
||||
func (c *codegen) convertStruct(lit *ast.CompositeLit) {
|
||||
// Create a new structScope to initialize and store
|
||||
// the positions of its variables.
|
||||
t, ok := c.typeInfo.TypeOf(lit).Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
log.Fatalf("the given literal is not of type struct: %v", lit)
|
||||
}
|
||||
strct := c.fctx.newStruct(t)
|
||||
|
||||
emitOpcode(c.prog, vm.Onop)
|
||||
emitInt(c.prog, int64(strct.t.NumFields()))
|
||||
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()
|
||||
// We need to locally store all the fields, even if they are not initialized.
|
||||
// We will initialize all fields to their "zero" value.
|
||||
for i := 0; i < strct.t.NumFields(); i++ {
|
||||
sField := strct.t.Field(i)
|
||||
fieldAdded := false
|
||||
|
||||
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)
|
||||
// Fields initialized by the program.
|
||||
for _, field := range lit.Elts {
|
||||
f := field.(*ast.KeyValueExpr)
|
||||
fieldName := f.Key.(*ast.Ident).Name
|
||||
|
||||
if sField.Name() == fieldName {
|
||||
ast.Walk(c, f.Value)
|
||||
pos := strct.loadField(fieldName)
|
||||
c.emitStoreLocal(pos)
|
||||
fieldAdded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if fieldAdded {
|
||||
continue
|
||||
}
|
||||
c.emitLoadConst(strct.typeAndValues[sField.Name()])
|
||||
c.emitStoreLocal(i)
|
||||
}
|
||||
emitOpcode(c.prog, vm.Ofromaltstack)
|
||||
}
|
||||
|
||||
func (c *codegen) convertToken(tok token.Token) {
|
||||
switch tok {
|
||||
case token.ADD_ASSIGN:
|
||||
emitOpcode(c.prog, vm.Oadd)
|
||||
case token.SUB_ASSIGN:
|
||||
emitOpcode(c.prog, vm.Osub)
|
||||
case token.MUL_ASSIGN:
|
||||
emitOpcode(c.prog, vm.Omul)
|
||||
case token.QUO_ASSIGN:
|
||||
emitOpcode(c.prog, vm.Odiv)
|
||||
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)
|
||||
case token.EQL, token.NEQ:
|
||||
emitOpcode(c.prog, vm.Onumequal)
|
||||
default:
|
||||
log.Fatalf("compiler could not convert token: %s", tok)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope {
|
||||
f := newFuncScope(decl, c.newLabel())
|
||||
c.funcs[f.name] = f
|
||||
return f
|
||||
}
|
||||
|
||||
// TODO: Don't know if we really use this. Check if it can be deleted.
|
||||
// If it's used once or twice, also remove this functions and
|
||||
// call .Types direcly. e.g. c.typeInfo.Types[expr]
|
||||
func (c *codegen) getTypeInfo(expr ast.Expr) types.TypeAndValue {
|
||||
return c.typeInfo.Types[expr]
|
||||
}
|
||||
|
@ -401,7 +500,7 @@ func isIdentBool(ident *ast.Ident) bool {
|
|||
}
|
||||
|
||||
// CodeGen is the function that compiles the program to bytecode.
|
||||
func CodeGen(f *ast.File, tInfo *types.Info) (*bytes.Buffer, error) {
|
||||
func CodeGen(f *ast.File, tInfo *types.Info, imports map[string]*archive) (*bytes.Buffer, error) {
|
||||
c := &codegen{
|
||||
prog: new(bytes.Buffer),
|
||||
l: []int{},
|
||||
|
@ -424,6 +523,11 @@ func CodeGen(f *ast.File, tInfo *types.Info) (*bytes.Buffer, error) {
|
|||
log.Fatal("could not find func main. did you forgot to declare it?")
|
||||
}
|
||||
|
||||
// Bring all imported functions into scope
|
||||
for _, arch := range imports {
|
||||
c.resolveFuncDecls(arch.f)
|
||||
}
|
||||
|
||||
c.resolveFuncDecls(f)
|
||||
c.convertFuncDecl(main)
|
||||
|
||||
|
@ -436,6 +540,19 @@ func CodeGen(f *ast.File, tInfo *types.Info) (*bytes.Buffer, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Generate code for the imported packages.
|
||||
for _, arch := range imports {
|
||||
c.typeInfo = arch.typeInfo
|
||||
for _, decl := range arch.f.Decls {
|
||||
switch n := decl.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if n.Name.Name != mainIdent {
|
||||
c.convertFuncDecl(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.writeJumps()
|
||||
|
||||
return c.prog, nil
|
||||
|
@ -470,3 +587,24 @@ func (c *codegen) writeJumps() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isSyscall(name string) bool {
|
||||
_, ok := vm.Syscalls[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
var noRetSyscalls = []string{
|
||||
"Notify", "Log", "Put", "Register", "Delete",
|
||||
"SetVotes", "ContractDestroy", "MerkleRoot", "Hash",
|
||||
"PrevHash", "GetHeader",
|
||||
}
|
||||
|
||||
// isNoRetSyscall checks if the syscall has a return value.
|
||||
func isNoRetSyscall(name string) bool {
|
||||
for _, s := range noRetSyscalls {
|
||||
if s == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue