Introduce gobal type analysis step. Add several tests for out of order.

This commit is contained in:
Marc Vertes
2018-09-19 18:34:56 +02:00
parent 6c29718aca
commit e82a223541
14 changed files with 323 additions and 52 deletions

15
_test/const1.go Normal file
View File

@@ -0,0 +1,15 @@
package main
type T struct {
a int
b string
}
var t = T{1, "hello"}
func main() {
println(t.a, t.b)
}
// Output:
// 1 hello

12
_test/method9.go Normal file
View File

@@ -0,0 +1,12 @@
package main
type Coord struct {
x, y int
}
func main() {
o := Coord{3, 4}
println(o.dist())
}
func (c Coord) dist() int { return c.x*c.x + c.y*c.y }

View File

@@ -4,6 +4,7 @@ import "fmt"
const (
SomeString = "constant string"
SomeInt = 1
)
type T1 struct {

10
_test/run11.go Normal file
View File

@@ -0,0 +1,10 @@
package main
func main() {
println(f())
}
func f() (int, int) { return 2, 3 }
// Output:
// 2 3

13
_test/run12.go Normal file
View File

@@ -0,0 +1,13 @@
package main
func f(a int) (int, int) {
return a + 1, a + 2
}
func main() {
a, b := f(3)
println(a, b)
}
// Output:
// 4 5

13
_test/run13.go Normal file
View File

@@ -0,0 +1,13 @@
package main
func main() {
a, b := f(3)
println(a, b)
}
func f(a int) (int, int) {
return a + 1, a + 2
}
// Output:
// 4 5

16
_test/type5.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"fmt"
"reflect"
)
type T int
func main() {
a := T(12)
fmt.Println(reflect.TypeOf(a))
}
// Output:
// int

16
_test/type6.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"fmt"
"reflect"
)
func main() {
a := T(12)
fmt.Println(reflect.TypeOf(a))
}
type T int
// Output:
// int

View File

@@ -2,7 +2,6 @@ package interp
import (
"log"
"path"
"reflect"
"strconv"
"unicode"
@@ -36,14 +35,15 @@ func (k SymKind) String() string {
// A Symbol represents an interpreter object such as type, constant, var, func, builtin or binary object
type Symbol struct {
kind SymKind
typ *Type // Type of value
node *Node // Node value if index is negative
index int // index of value in frame or -1
val interface{} // default value (used for constants)
path string // package path if typ.cat is SrcPkgT or BinPkgT
builtin Builtin // Builtin function or nil
global bool // true if symbol is defined in global space
kind SymKind
typ *Type // Type of value
node *Node // Node value if index is negative
index int // index of value in frame or -1
val interface{} // default value (used for constants)
path string // package path if typ.cat is SrcPkgT or BinPkgT
builtin Builtin // Builtin function or nil
global bool // true if symbol is defined in global space
constant bool // true if symbol value is constant
}
// A SymMap stores symbols indexed by name
@@ -145,20 +145,18 @@ func (interp *Interpreter) Cfg(root *Node) []*Node {
interp.scope[pkgName] = scope.push(0)
}
scope = interp.scope[pkgName]
log.Println("scope:", scope)
scope.size = interp.fsize
if pkg, ok := interp.Exports[pkgName]; ok {
exports = pkg
expval = interp.Expval[pkgName]
unresolved = interp.unresolved[pkgName]
} else {
x := make(BinMap)
exports = &x
exports = &BinMap{}
interp.Exports[pkgName] = exports
y := make(ValueMap)
expval = &y
expval = &ValueMap{}
interp.Expval[pkgName] = expval
z := make(UnresolvedMap)
unresolved = &z
unresolved = &UnresolvedMap{}
interp.unresolved[pkgName] = unresolved
}
@@ -216,17 +214,19 @@ func (interp *Interpreter) Cfg(root *Node) []*Node {
scope = scope.push(0)
case TypeSpec:
// Type analysis is performed recursively and no post-order processing
// needs to be done for types, so do not dive in subtree
if n.child[1].kind == Ident {
// Create a type alias of an existing one
n.typ = &Type{cat: AliasT, val: nodeType(interp, scope, n.child[1])}
} else {
// Define a new type
n.typ = nodeType(interp, scope, n.child[1])
}
scope.sym[n.child[0].ident] = &Symbol{kind: Typ, typ: n.typ}
// TODO: export type for use by runtime
/*
// Type analysis is performed recursively and no post-order processing
// needs to be done for types, so do not dive in subtree
if n.child[1].kind == Ident {
// Create a type alias of an existing one
n.typ = &Type{cat: AliasT, val: nodeType(interp, scope, n.child[1])}
} else {
// Define a new type
n.typ = nodeType(interp, scope, n.child[1])
}
scope.sym[n.child[0].ident] = &Symbol{kind: Typ, typ: n.typ}
// TODO: export type for use by runtime
*/
return false
case ArrayType, BasicLit, ChanType, MapType, StructType:
@@ -787,29 +787,30 @@ func (interp *Interpreter) Cfg(root *Node) []*Node {
fbody.tnext = n
scope = scope.pop()
case ImportSpec:
var name, ipath string
if len(n.child) == 2 {
ipath = n.child[1].val.(string)
name = n.child[0].ident
} else {
ipath = n.child[0].val.(string)
name = path.Base(ipath)
}
if pkg, ok := interp.binValue[ipath]; ok {
if name == "." {
for n, s := range pkg {
scope.sym[n] = &Symbol{typ: &Type{cat: BinT}, val: s}
/*
case ImportSpec:
var name, ipath string
if len(n.child) == 2 {
ipath = n.child[1].val.(string)
name = n.child[0].ident
} else {
ipath = n.child[0].val.(string)
name = path.Base(ipath)
}
} else {
scope.sym[name] = &Symbol{typ: &Type{cat: BinPkgT}, path: ipath}
}
} else {
// TODO: make sure we do not import a src package more than once
interp.importSrcFile(ipath)
scope.sym[name] = &Symbol{typ: &Type{cat: SrcPkgT}, path: ipath}
}
if pkg, ok := interp.binValue[ipath]; ok {
if name == "." {
for n, s := range pkg {
scope.sym[n] = &Symbol{typ: &Type{cat: BinT}, val: s}
}
} else {
scope.sym[name] = &Symbol{typ: &Type{cat: BinPkgT}, path: ipath}
}
} else {
// TODO: make sure we do not import a src package more than once
interp.importSrcFile(ipath)
scope.sym[name] = &Symbol{typ: &Type{cat: SrcPkgT}, path: ipath}
}
*/
case KeyValueExpr:
wireChild(n)

71
interp/gta.go Normal file
View File

@@ -0,0 +1,71 @@
package interp
import (
"log"
"path"
)
// Gta performs a global types analysis on the AST, registering types,
// variables and functions at package level, prior to CFG. All function
// bodies are skipped.
// GTA is necessary to handle out of order declarations and multiple
// source files packages.
func (interp *Interpreter) Gta(root *Node) {
var pkgName string
scope := interp.universe
root.Walk(func(n *Node) bool {
log.Println(n.index, n.kind)
switch n.kind {
case File:
pkgName = n.child[0].ident
if _, ok := interp.scope[pkgName]; !ok {
interp.scope[pkgName] = scope.push(0)
}
scope = interp.scope[pkgName]
case FuncDecl:
scope.sym[n.child[1].ident] = &Symbol{
kind: Func,
typ: nodeType(interp, scope, n.child[2]),
node: n,
}
return false
case ImportSpec:
var name, ipath string
if len(n.child) == 2 {
ipath = n.child[1].val.(string)
name = n.child[0].ident
} else {
ipath = n.child[0].val.(string)
name = path.Base(ipath)
}
if pkg, ok := interp.binValue[ipath]; ok {
if name == "." {
for n, s := range pkg {
scope.sym[n] = &Symbol{typ: &Type{cat: BinT}, val: s}
}
} else {
scope.sym[name] = &Symbol{typ: &Type{cat: BinPkgT}, path: ipath}
}
} else {
// TODO: make sure we do not import a src package more than once
interp.importSrcFile(ipath)
scope.sym[name] = &Symbol{typ: &Type{cat: SrcPkgT}, path: ipath}
}
case TypeSpec:
if n.child[1].kind == Ident {
n.typ = &Type{cat: AliasT, val: nodeType(interp, scope, n.child[1])}
} else {
n.typ = nodeType(interp, scope, n.child[1])
}
scope.sym[n.child[0].ident] = &Symbol{kind: Typ, typ: n.typ}
return false
}
return true
}, nil)
log.Println(interp.scope[pkgName])
}

View File

@@ -166,6 +166,8 @@ func (i *Interpreter) Eval(src string) string {
root.AstDot(DotX())
}
i.Gta(root)
// Annotate AST with CFG infos
initNodes := i.Cfg(root)
if sym := i.scope[pkgName].sym[i.Entry]; sym != nil {

View File

@@ -554,6 +554,28 @@ func main() {
}
func Example_const1() {
src := `
package main
type T struct {
a int
b string
}
var t = T{1, "hello"}
func main() {
println(t.a, t.b)
}
`
i := NewInterpreter(Opt{Entry: "main"})
i.Eval(src)
// Output:
// 1 hello
}
func Example_cont() {
src := `
package main
@@ -2047,10 +2069,13 @@ func main() {
println(f())
}
func f() (int, int) { return 2, 3 }`
func f() (int, int) { return 2, 3 }
`
i := NewInterpreter(Opt{Entry: "main"})
i.Eval(src)
// Output:
// 2 3
}
func Example_run12() {
@@ -2064,10 +2089,33 @@ func f(a int) (int, int) {
func main() {
a, b := f(3)
println(a, b)
}`
}
`
i := NewInterpreter(Opt{Entry: "main"})
i.Eval(src)
// Output:
// 4 5
}
func Example_run13() {
src := `
package main
func main() {
a, b := f(3)
println(a, b)
}
func f(a int) (int, int) {
return a + 1, a + 2
}
`
i := NewInterpreter(Opt{Entry: "main"})
i.Eval(src)
// Output:
// 4 5
}
func Example_run4() {
@@ -3239,6 +3287,52 @@ func main() {
// int32
}
func Example_type5() {
src := `
package main
import (
"fmt"
"reflect"
)
type T int
func main() {
a := T(12)
fmt.Println(reflect.TypeOf(a))
}
`
i := NewInterpreter(Opt{Entry: "main"})
i.Eval(src)
// Output:
// int
}
func Example_type6() {
src := `
package main
import (
"fmt"
"reflect"
)
func main() {
a := T(12)
fmt.Println(reflect.TypeOf(a))
}
type T int
`
i := NewInterpreter(Opt{Entry: "main"})
i.Eval(src)
// Output:
// int
}
func Example_var() {
src := `
package main

View File

@@ -693,7 +693,8 @@ func compositeSparse(n *Node, f *Frame) {
}
func _range(n *Node, f *Frame) {
i, index := 0, n.child[0].findex
i := 0
index := n.child[0].findex
if f.data[index] != nil {
i = f.data[index].(int) + 1
}

View File

@@ -39,14 +39,20 @@ func (interp *Interpreter) importSrcFile(path string) {
}
pkgName, root = interp.Ast(string(buf))
initNodes = append(initNodes, interp.Cfg(root)...)
rootNodes = append(rootNodes, root)
if interp.AstDot {
root.AstDot(DotX())
}
interp.Gta(root)
}
// Generate control flow graphs
for _, root := range rootNodes {
initNodes = append(initNodes, interp.Cfg(root)...)
}
// Fix nodes with unresolved symbols due to out of order parsing
log.Println("unresolved:", *(interp.unresolved[pkgName]))
for _, nodes := range *(interp.unresolved[pkgName]) {
for _, n := range nodes {
n.typ = n.sym.typ