Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: top sort var/const globals #1854

Merged
merged 17 commits into from
Apr 9, 2024
19 changes: 18 additions & 1 deletion gnovm/pkg/gnolang/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,23 @@ type Attributes struct {
data map[interface{}]interface{} // not persisted
}

func (attr *Attributes) Copy() Attributes {
if attr == nil {
return Attributes{}
}

data := make(map[interface{}]interface{})
for k, v := range attr.data {
data[k] = v
}

return Attributes{
Line: attr.Line,
Label: attr.Label,
data: data,
}
}

func (attr *Attributes) GetLine() int {
return attr.Line
}
Expand Down Expand Up @@ -1614,7 +1631,7 @@ func (sb *StaticBlock) GetPathForName(store Store, n Name) ValuePath {
bp = bp.GetParentNode(store)
gen++
if 0xff < gen {
panic("value path depth overflow")
panic("GetPathForName: value path depth overflow")
}
}
}
Expand Down
35 changes: 16 additions & 19 deletions gnovm/pkg/gnolang/preprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,21 @@ import (
// Anything predefined or preprocessed here get skipped during the Preprocess
// phase.
func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) {
for _, fn := range fset.Files {
decls, err := sortValueDeps(fn.Decls)
petar-dambovaliev marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
panic(err)
}

fn.Decls = decls
}

// First, initialize all file nodes and connect to package node.
for _, fn := range fset.Files {
SetNodeLocations(pn.PkgPath, string(fn.Name), fn)
fn.InitStaticBlock(fn, pn)
}

// NOTE: much of what follows is duplicated for a single *FileNode
// in the main Preprocess translation function. Keep synced.

Expand All @@ -29,29 +39,22 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) {
d := fn.Decls[i]
switch d.(type) {
case *ImportDecl:
if d.GetAttribute(ATTR_PREDEFINED) == true {
// skip declarations already predefined
// (e.g. through recursion for a
// dependent)
} else {
if d.GetAttribute(ATTR_PREDEFINED) != true {
// recursively predefine dependencies.
d2, _ := predefineNow(store, fn, d)
fn.Decls[i] = d2
}
}
}
}

// Predefine all type decls decls.
for _, fn := range fset.Files {
for i := 0; i < len(fn.Decls); i++ {
d := fn.Decls[i]
switch d.(type) {
case *TypeDecl:
if d.GetAttribute(ATTR_PREDEFINED) == true {
// skip declarations already predefined
// (e.g. through recursion for a
// dependent)
} else {
if d.GetAttribute(ATTR_PREDEFINED) != true {
// recursively predefine dependencies.
d2, _ := predefineNow(store, fn, d)
fn.Decls[i] = d2
Expand All @@ -65,27 +68,21 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) {
d := fn.Decls[i]
switch d.(type) {
case *FuncDecl:
if d.GetAttribute(ATTR_PREDEFINED) == true {
// skip declarations already predefined
// (e.g. through recursion for a
// dependent)
} else {
if d.GetAttribute(ATTR_PREDEFINED) != true {
// recursively predefine dependencies.
d2, _ := predefineNow(store, fn, d)
fn.Decls[i] = d2
}
}
}
}

// Finally, predefine other decls and
// preprocess ValueDecls..
for _, fn := range fset.Files {
for i := 0; i < len(fn.Decls); i++ {
d := fn.Decls[i]
if d.GetAttribute(ATTR_PREDEFINED) == true {
// skip declarations already predefined (e.g.
// through recursion for a dependent)
} else {
if d.GetAttribute(ATTR_PREDEFINED) != true {
// recursively predefine dependencies.
d2, _ := predefineNow(store, fn, d)
fn.Decls[i] = d2
Expand Down
245 changes: 245 additions & 0 deletions gnovm/pkg/gnolang/value_decl_dep_graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
package gnolang

import (
"fmt"
"slices"
"strings"
)

// sortValueDeps creates a new topologically sorted
// decl slice ready for processing in order
func sortValueDeps(decls Decls) (Decls, error) {
graph := &graph{
edges: make(map[string][]string),
vertices: make([]string, 0),
}

otherDecls := make(Decls, 0)

for i := 0; i < len(decls); i++ {
d := decls[i]
vd, ok := d.(*ValueDecl)

if !ok {
otherDecls = append(otherDecls, d)
continue
}

if isTuple(vd) {
_, ok := vd.Values[0].(*CallExpr)
if ok {
graph.addVertex(vd.NameExprs.String())
continue
}
}

for j := 0; j < len(vd.NameExprs); j++ {
graph.addVertex(string(vd.NameExprs[j].Name))
}
}

for i := 0; i < len(decls); i++ {
d := decls[i]
vd, ok := d.(*ValueDecl)

if !ok {
continue
}

if isTuple(vd) {
ce, ok := vd.Values[0].(*CallExpr)
if ok {
addDepFromExpr(graph, vd.NameExprs.String(), ce)
continue
}
}

for j := 0; j < len(vd.NameExprs); j++ {
if len(vd.Values) > j {
addDepFromExpr(graph, string(vd.NameExprs[j].Name), vd.Values[j])
}
}
}

sorted := make(Decls, 0)

for _, node := range graph.topologicalSort() {
var dd Decl

for _, decl := range decls {
vd, ok := decl.(*ValueDecl)

if !ok {
continue
}

if isCompoundNode(node) {
dd = processCompound(node, vd, decl)
break
}

for i, nameExpr := range vd.NameExprs {
if string(nameExpr.Name) == node {
if len(vd.Values) > i {
dd = &ValueDecl{
Attributes: vd.Attributes.Copy(),
petar-dambovaliev marked this conversation as resolved.
Show resolved Hide resolved
NameExprs: []NameExpr{nameExpr},
Type: vd.Type,
Values: []Expr{vd.Values[i]},
Const: vd.Const,
}
break
} else {
dd = vd
break
}
}
}
}

if dd == nil {
continue
}

sorted = append(sorted, dd)
}

slices.Reverse(sorted)

otherDecls = append(otherDecls, sorted...)

return otherDecls, nil
}

func addDepFromExpr(dg *graph, fromNode string, expr Expr) {
switch e := expr.(type) {
case *FuncLitExpr:
for _, stmt := range e.Body {
addDepFromExprStmt(dg, fromNode, stmt)
}
case *CallExpr:
addDepFromExpr(dg, fromNode, e.Func)

for _, arg := range e.Args {
addDepFromExpr(dg, fromNode, arg)
}
case *NameExpr:
if isUverseName(e.Name) {
break
}

toNode := string(e.Name)
dg.addEdge(fromNode, toNode)
}
}

func addDepFromExprStmt(dg *graph, fromNode string, stmt Stmt) {
switch e := stmt.(type) {
case *ExprStmt:
addDepFromExpr(dg, fromNode, e.X)
case *IfStmt:
addDepFromExprStmt(dg, fromNode, e.Init)
addDepFromExpr(dg, fromNode, e.Cond)

for _, stm := range e.Then.Body {
addDepFromExprStmt(dg, fromNode, stm)
}
for _, stm := range e.Else.Body {
addDepFromExprStmt(dg, fromNode, stm)
}
case *ReturnStmt:
for _, stm := range e.Results {
addDepFromExpr(dg, fromNode, stm)
}
case *AssignStmt:
for _, stm := range e.Rhs {
addDepFromExpr(dg, fromNode, stm)
}
case *SwitchStmt:
addDepFromExpr(dg, fromNode, e.X)
for _, clause := range e.Clauses {
addDepFromExpr(dg, fromNode, clause.bodyStmt.Cond)
for _, s := range clause.bodyStmt.Body {
addDepFromExprStmt(dg, fromNode, s)
}
}
case *ForStmt:
addDepFromExpr(dg, fromNode, e.Cond)
for _, s := range e.bodyStmt.Body {
addDepFromExprStmt(dg, fromNode, s)
}
case *BlockStmt:
for _, s := range e.Block.bodyStmt.Body {
addDepFromExprStmt(dg, fromNode, s)
}
}
}

type graph struct {
edges map[string][]string
vertices []string
}

func (g *graph) addEdge(u, v string) {
g.edges[u] = append(g.edges[u], v)
}

func (g *graph) addVertex(v string) {
g.vertices = append(g.vertices, v)
}

func (g *graph) topologicalSortUtil(v string, visited map[string]bool, stack *[]string) {
visited[v] = true

for _, u := range g.edges[v] {
if !visited[u] {
g.topologicalSortUtil(u, visited, stack)
}
}

*stack = append([]string{v}, *stack...)
}

func (g *graph) topologicalSort() []string {
stack := make([]string, 0)
visited := make(map[string]bool)

for _, v := range g.vertices {
if !visited[v] {
g.topologicalSortUtil(v, visited, &stack)
}
}

return stack
}

func isTuple(vd *ValueDecl) bool {
return len(vd.NameExprs) > len(vd.Values) && len(vd.Values) > 0
}

func isCompoundNode(node string) bool {
return strings.Contains(node, ", ")
}

func processCompound(node string, vd *ValueDecl, decl Decl) Decl {
names := strings.Split(node, ", ")

if len(names) != len(vd.NameExprs) {
panic("should not happen")
}

equal := true

for i, name := range names {
if vd.NameExprs[i].String() != name {
equal = false
break
}
}

if !equal {
panic(fmt.Sprintf("names: %+v != nameExprs: %+v\n", names, vd.NameExprs))
}

return decl
}
14 changes: 14 additions & 0 deletions gnovm/tests/files/var18.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package main

func main() {
println(a)
println(b)
}

func r2() (int, int) { return 1, 2 }

var a, b int = r2()

// Output:
// 1
// 2
14 changes: 14 additions & 0 deletions gnovm/tests/files/var19.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package main

func main() {
println(a)
println(b)
println(c)
}

var a, b, c = 1, a + 1, b + 1

// Output:
// 1
// 2
// 3
Loading
Loading