Skip to content

Commit

Permalink
fix: top sort var/const globals (#1854)
Browse files Browse the repository at this point in the history
This PR fixes [this](#1849) and
[this](#1463).

Before preprocessing, a dependency graph is created and edges between
the nodes which represent the relationship between global `var` and
`const` declarations.
Then, a new slice of declarations is created that is topologically
sorted.
This enables the rest of the preprocessing code to work the way it is
now.

Small scale refactoring is included by removing unnecessary else
statements in `PredefineFileSet`.
  • Loading branch information
petar-dambovaliev authored Apr 9, 2024
1 parent 60c09b9 commit 3b95486
Show file tree
Hide file tree
Showing 7 changed files with 341 additions and 20 deletions.
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)
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(),
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

0 comments on commit 3b95486

Please sign in to comment.