Commit efa6106d authored by Hugo Moreau's avatar Hugo Moreau
Browse files

Transform: Dependencies transform created

A new transform has been created, its goal is to collect informations
about all statements dependencies in order to use it to decide which
statements can be ignored.

The `Dependencies` transform for now collect all this informations:
 - Global Variables
 - Variables interdependencies
 - Function details (Parameters, Variables used, Number of statements)

Two debug functions have been added for a better understanding, the
first one prints all informations. The second output a GraphViz graph,
so we can visualize `variables` interdependencies and their respectives
scopes.

 * main.go : Modified.
   transform/dependencies.go : Here.
parent 72aecc7b
Pipeline #28071 passed with stage
in 2 minutes and 40 seconds
......@@ -103,6 +103,7 @@ func compileTo(filepath string, src []byte, file *os.File,
localvariable := &transform.LocalVariableAssignments{Global: true}
res := transform.ApplyAll(f, meta, []transform.Transform{
&transform.Dependencies{},
// &transform.Debug{}, // only for debuging a special transformation
&transform.Alive{},
&transform.Channel{
......
package transform
import (
"bytes"
"fmt"
"go/ast"
"io"
"io/ioutil"
"log"
"os"
)
type VarDetails struct {
UsedStmt []int
VarDependencies []*ast.Object
}
type FuncDetails struct {
BodyStmt []ast.Stmt
ToIgnoreStmt []int
Parameters []*ast.Field
Variables map[*ast.Object]*VarDetails
Visited bool
}
type FuncInfos struct {
Body *ast.BlockStmt
Obj *ast.Object
Index int
Variables []*ast.Object
}
type Dependencies struct {
currentFunc FuncInfos
FuncsVar map[*ast.Object]*FuncDetails
GlobalVar []*ast.Object
}
func findObj(objList []*ast.Object, obj *ast.Object) bool {
for _, val := range objList {
if val == obj {
return true
}
}
return false
}
func (t *Dependencies) isGlobalVar(obj *ast.Object) bool {
return findObj(t.GlobalVar, obj)
}
// Init all Dependencies stuct's map at the begining of the transform.
func (t *Dependencies) init(file *ast.File) {
t.FuncsVar = make(map[*ast.Object]*FuncDetails)
for _, val := range file.Scope.Objects {
if val.Kind == ast.Var {
t.GlobalVar = append(t.GlobalVar, val)
} else if val.Kind == ast.Fun {
t.FuncsVar[val] = &FuncDetails{
ToIgnoreStmt: []int{},
Variables: make(map[*ast.Object]*VarDetails),
Visited: false,
}
switch decl := val.Decl.(type) {
case *ast.FuncDecl:
t.FuncsVar[val].Parameters = decl.Type.Params.List
t.FuncsVar[val].BodyStmt = decl.Body.List
}
}
}
}
func (infos *FuncInfos) getIndex() int {
return infos.Index - 1
}
func (infos *FuncInfos) convert(funcsVar map[*ast.Object]*FuncDetails) {
if len(infos.Variables) == 0 || infos.Index <= 0 || infos.Obj == nil {
return
}
for _, val := range infos.Variables {
if _, ok := funcsVar[infos.Obj].Variables[val]; !ok {
funcsVar[infos.Obj].Variables[val] = &VarDetails{
UsedStmt: []int{},
VarDependencies: []*ast.Object{},
}
}
funcsVar[infos.Obj].Variables[val].UsedStmt =
append(funcsVar[infos.Obj].Variables[val].UsedStmt, infos.getIndex())
for _, toAdd := range infos.Variables {
if toAdd != val &&
!findObj(funcsVar[infos.Obj].Variables[val].VarDependencies, toAdd) {
funcsVar[infos.Obj].Variables[val].VarDependencies =
append(funcsVar[infos.Obj].Variables[val].VarDependencies, toAdd)
}
}
}
}
func (infos *FuncInfos) registerVariables(funcsVar map[*ast.Object]*FuncDetails, node ast.Node) bool {
if infos.Index < len(infos.Body.List) &&
node == infos.Body.List[infos.Index] {
infos.convert(funcsVar)
infos.Index++
infos.Variables = []*ast.Object{}
return true
}
return false
}
func (infos *FuncInfos) init(funcDecl *ast.FuncDecl) {
infos.Obj = funcDecl.Name.Obj
infos.Body = funcDecl.Body
infos.Index = 0
infos.Variables = []*ast.Object{}
}
// Pre traversal applies the transformation.
func (t *Dependencies) Pre(meta *Meta, v *Visitor) bool {
c := v.Cursor()
if t.currentFunc.Obj != nil {
t.currentFunc.registerVariables(t.FuncsVar, c.Node())
}
switch node := c.Node().(type) {
case *ast.File:
t.init(node)
case *ast.FuncDecl:
t.FuncsVar[node.Name.Obj].Visited = true
t.currentFunc.convert(t.FuncsVar)
t.currentFunc.init(node)
case *ast.GenDecl:
switch c.Parent().(type) {
case *ast.File:
t.currentFunc.Body = nil
t.currentFunc.Obj = nil
}
case *ast.Ident:
if t.currentFunc.Obj == nil {
return true
}
if node.Obj != nil && !findObj(t.currentFunc.Variables, node.Obj) {
if node.Obj.Kind == ast.Var {
t.currentFunc.Variables = append(t.currentFunc.Variables, node.Obj)
}
}
}
return true
}
// Post traversal applies the transformation.
func (t *Dependencies) Post(meta *Meta, v *Visitor) bool {
c := v.Cursor()
switch c.Node().(type) {
case *ast.File:
if t.currentFunc.Obj != nil {
t.currentFunc.convert(t.FuncsVar)
}
redirectFuncOutput("main.dot", t.PrintGraph)
fmt.Println("All infomations are stored in `main.dot` file, you may use GraphViz:")
fmt.Println(" dot -Tpng main.dot -o main.png")
fmt.Println("Further informations here:")
t.Print()
}
return true
}
// Temporary function to redirect stdout.
func redirectFuncOutput(filename string, fun func()) {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
fun()
outC := make(chan string)
go func() {
var buf bytes.Buffer
io.Copy(&buf, r)
outC <- buf.String()
}()
w.Close()
os.Stdout = old
out := <-outC
err := ioutil.WriteFile(filename, []byte(out), 0644)
if err != nil {
log.Fatal(err)
}
}
// Print all Dependencies informations, debug purpose only.
func (t *Dependencies) Print() {
fmt.Println("Global variables:")
for _, val := range t.GlobalVar {
fmt.Printf(" - Name: %s, Address: %p\n", val.Name, val)
}
fmt.Println("Functions informations:")
for key, val := range t.FuncsVar {
fmt.Println(" - Name:", key.Name)
fmt.Println(" - Nb Statements:", len(val.BodyStmt))
fmt.Println(" - Parameters:")
for _, value := range val.Parameters {
for _, name := range value.Names {
fmt.Printf(" - Name: %s, Address: %p\n", name.Obj.Name, name.Obj)
}
}
fmt.Println(" - Variables:")
for key2, val2 := range val.Variables {
fmt.Printf(" - Name: %s, Address: %p\n", key2.Name, key2)
fmt.Println(" - Used Statements: ", val2.UsedStmt, "Ratio:", len(val2.UsedStmt), "/", len(val.BodyStmt))
fmt.Println(" - Variables dependencies:")
for _, value := range val2.VarDependencies {
fmt.Printf(" - Name: %s, Address: %p\n", value.Name, value)
}
}
}
}
// Output a graphviz graph.
func (t *Dependencies) PrintGraph() {
indent := &Indent{}
fmt.Println("graph G {")
fmt.Printf("%*s%s\n", indent.indent().N, "", "label=\"package main\";")
fmt.Printf("%*s%s\n", indent.N, "", "compound=true;")
// Global variables
fmt.Printf("%*s%s\n", indent.N, "", "subgraph cluster_0 {")
fmt.Printf("%*s%s\n", indent.indent().N, "", "label=\"global variables\"")
fmt.Printf("%*s%s\n", indent.N, "", "color=blue")
for _, val := range t.GlobalVar {
fmt.Printf("%*s%#p[label=\"%s\"]\n", indent.N, "", val, val.Name)
}
fmt.Printf("%*s%s\n", indent.unindent().N, "", "}")
// Function variables
cluster := 1
double := []Pair{}
for key, val := range t.FuncsVar {
fmt.Printf("%*ssubgraph cluster_%d {\n", indent.N, "", cluster)
fmt.Printf("%*slabel=\"%s\"\n", indent.indent().N, "", "func "+key.Name)
fmt.Printf("%*s%s\n", indent.N, "", "color=lightgray")
fmt.Printf("%*s%s\n", indent.N, "", "func_"+key.Name+"[style=invis,shape=point]")
for _, value := range val.Parameters {
for _, name := range value.Names {
fmt.Printf("%*s%#p[label=\"%s\"]\n", indent.N, "", name.Obj, name.Obj.Name)
}
}
for key2, val2 := range val.Variables {
fmt.Printf("%*s%#p[label=\"%s\"]\n", indent.N, "", key2, key2.Name)
x := fmt.Sprintf("%#p", key2)
if len(val2.VarDependencies) == 0 && t.isGlobalVar(key2) {
if !findPair(double, Pair{x, x, key.Name}) {
fmt.Printf("%*s%#p -- %s[color=indigo,lhead=\"cluster_%d\"]\n", indent.N, "", key2, "func_"+key.Name, cluster)
double = append(double, Pair{x, x, key.Name})
}
}
for _, value := range val2.VarDependencies {
y := fmt.Sprintf("%#p", value)
if !findPair(double, Pair{x, y, key.Name}) {
if t.isGlobalVar(key2) && t.isGlobalVar(value) {
fmt.Printf("%*s%#p -- %#p[label=\"%s\",color=indigo,fontcolor=indigo]\n", indent.N, "", key2, value, "func "+key.Name)
} else {
fmt.Printf("%*s%#p -- %#p[color=indigo]\n", indent.N, "", key2, value)
}
double = append(double, Pair{x, y, key.Name})
}
}
}
fmt.Printf("%*s}\n", indent.unindent().N, "")
cluster++
}
fmt.Println("}")
}
type Pair struct {
x, y, z string
}
func findPair(pairList []Pair, pair Pair) bool {
for _, val := range pairList {
if val.x == pair.x && val.y == pair.y && pair.z == val.z {
return true
} else if val.y == pair.x && val.x == pair.y && pair.z == val.z {
return true
}
}
return false
}
type Indent struct {
N int
}
func (i *Indent) indent() *Indent {
i.N += 4
return i
}
func (i *Indent) unindent() *Indent {
if i.N <= 0 {
i.N = 0
return i
}
i.N -= 4
return i
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment