Global variables: a new way of handling it

As we saw previously, we want to access global variables from any blackboxed functions, regardless if it fit all cases (recursion mechanics, inner call to other functions…) or not. We had a starting idea, it was to replace all global variables identifier to some accessor functions calls. It seemed to be a good idea at first, but with a little more hindsight, we may encounter a new problem.

Let me explain it to you, Go2Pins generates a new Go files with our system’s state, it is represented by a vector of integer variables. This vector is computed in order to obtain our Kripke Structure, each states contains our vector with its current state.

If we want to access our global variables value, we need the actual vector. Our new Getter and Setter functions are not currently able to access it, to do so we need to give it as an argument to our newly created functions and consequently to our Blackbox functions.

You can now see where is the new problem, don’t you ? Why using some accessor functions to get or set our global variables value, when you can easily access it via the state vector directly ?

This idea seems much clearer and won’t require that much efforts. If you want to see the current work before it will disappear, you can see it here. If you want to have a closer look on what we’re going to handle this, I invite to continue reading.

State vector open to the world

I hope you already took time to “play” a little with Go2Pins, if you didn’t, don’t worry, I will explain to you what I want you to see. Let’s retake our previous example, fibonacci.go:

/* fibonacci.go */
package main

func fibonacci(n int) int {
    n0 := 0
    n1 := 1
    for i := 0; i < n; i++ {
        n2 := n0 + n1
        n0 = n1
        n1 = n2
    }
    return n1
}

func main() {
    fibonacci(5)
}

If we run Go2Pins on it and we decide to open our new fibonacci.go you may see something like this: Generated var and type definitions for fibonacci.go

/* code before */
const G2PRersActive = 0
const G2PRersPos = 0

var G2PRersValues = []int{}

const G2PStateSize = 15

type G2PStateType [G2PStateSize]int

var G2PVariableNames = [15]*C.char{
	C.CString("FunctionCounter"),                 // 0
	C.CString("LabelCounter"),                    // 1
	C.CString("main_fibonacci__res0"),            // 2
	C.CString("main_fibonacci_n"),                // 3
	C.CString("main_fibonacci__caller"),          // 4
	C.CString("main_fibonacci__callerLabel"),     // 5
	C.CString("main_main__caller"),               // 6
	C.CString("main_main__callerLabel"),          // 7
	C.CString("main_fibonacci_n0"),               // 8
	C.CString("main_fibonacci_G2P_is_alive_n0"),  // 9
	C.CString("main_fibonacci_n1"),               // 10
	C.CString("main_fibonacci_G2P_is_alive_n1"),  // 11
	C.CString("main_fibonacci_i"),                // 12
	C.CString("main_fibonacci_n2"),               // 13
	C.CString("main_fibonacci_G2P_is_alive_n2"),  // 14
}
var G2PGoroutines = []Goroutine{}
var G2PChannels = []Channel{}
/* code after */


If you are a newcomer, you won’t be able to understand it at first, spoiler you don’t need to, but let’s have some little explain anyway:

  • G2PStateSize is the size of our vector of int.
  • G2PStateType is our representation of our vector of int.
  • G2PVariableNames is a vector representing the name of each value.
  • G2PGoroutines is an array listing our Goroutines.
  • G2PChannels is an array listing our Channels.

What we want is to have access to our type, and further our variables from anywhere. Since our type is declared in our main package, we can’t access it from another, redeclaring this type in another package won’t work either. So what we must do is create a new package, let’s call it structs since we will try to store as many things as possible here.

Interacting with our State Vector from anywhere

Main idea

In order to give access to Blackbox package, and maybe future packages(?), let’s suppose we compute Go2Pins on our fibonacci.go file. Our newly created main.go and fibonacci.go files have to be able to access a new package named structs. But more important this new package should contain all our variables and typedef.
For this part, nothing really difficult, we juste need to create a new boilerplate, see it here.

// structs/structs.go
package structs

type GoroutineFunc func(G2PStateType) G2PStateType

// A Goroutine contains information related to a goroutine's
// representation inside go2pins
type Goroutine struct {
	Status      int           // index in vector for status variable
	LineCounter int           // index in vector for line counter variable
	Func        GoroutineFunc // duplicated function to launch
}

// A Channel contains informations realted to a channel's
// representation inside go2pins
type Channel struct {
	State  int // index in vector of the beggining of State's array
	Value  int // index in vector of the beggining of Value's array
	Length int // length of the arrays (same for the two)
}


Now that we have our boilerplate, we will add our variables and type definitions here instead of the fibonacci.go file and that’s pretty all. Our comments will disappear when we decide to add our new variables and typedef. Using go/parser, it is difficult to parse comments since it uses token position, it will break our file.I don’t know if it’s really a big deal, in the benefit of the doubt, I let you check the doc, mode ParseComments. However if we still want it, check ast.NewCommentMap().

Blackbox functions using our State vector

Ok, good ! We are now able to access to our State vector type from anywhere, it is now the turn to Blackbox to do so. Let’s dump our previous code and start again, Good bye accessors, welcome structs package.
Let’s not dump all our code, we can use again some parts,

  • PreGlobal transform will still detect our Global variables, we need it in every case, because in the case some Global variables are declared at the end of the file, it will generate some errors.
  • Global will now detect which function uses global variables. Why that ? Because we want to know if we want to use our structs package or not, even if we add a _ before(see blank import) we can import it and not use it, but let’s make it properly. At the same time it will add our State vector as an argument to our Blackboxed functions.
  • PostGlobal will do as it always did, it will inject parameters to our Blackbox functions call.


Let’s take this file and launch Go2Pins on it with foo() as a Blackbox functions.

package main

var a = 2

func foo() {
    a = 42
}

func main() {
    foo()
}

will become:

package main

var a = 2

func foo(G2PState *structs.G2PStateType) {
    a = 42
}

func main() {
    foo(&G2PState)
}

GlobalToState is a new transform, it happens after all transform, because it need to access our State Vector, seeking for our corresponding variables in order to access it in our Blackbox package.

Transform’s overview : Global Blackbox

It could have been easier, slices were at the corner of the street

I figured out after implementing our new structs package that it was not necessary to have the type of our array to access it, thanks to slice. Slices are considered as references to arrays in golang, take a closer look here.

package blackbox

import "output/structs"

func Foo(G2PState *structs.G2PStateType) {
    G2PState[6] = 42
}

Function call:

blackbox.Foo(&G2PState)

could have been:

package blackbox

func Foo(G2PState []int) {
    G2PState[6] = 42
}

Function call:

blackbox.Foo(G2pState[:])

Anyway, it is done, we will see in the future, if we decide to use slices instead or not. But now the output directory look a little bit more organized.

Enchancement

I finally figured out how to manage our packages in the output directory in an easier way, the problem was that we needed to specify our Go2Pins module path everytime, and in the case that the output was outside the Go2Pins directory, it would have generated a problem.

We now create a go module for the output directory directly. We have a new Makefile rule go.mod.

go.mod:
    go mod init {{.Output}}

We go from this:

import "gitlab.lrde.epita.fr/spot/go2pins/output/blackbox"

to this:

import "output/blackbox" // output is the output directory name

Problem encountered

  • It appeared that not blackboxed functions call were modified anyway.
  • LocalVariableAssignements transform need to be corrected, if you declare a global variable after accessing to it, it will fail to recover it.
  • For some reason, inner call in a function call doesn’t work. This does not work:
package main

import "fmt"

func foo() int {
	return bar()
}

func bar() int {
	return 42
}

func main() {
	fmt.Println(foo())
}

This does:

package main

import "fmt"

func foo() int {
	return bar()
}

func bar() int {
	return 42
}

func main() {
        tmp := foo()
        fmt.Println(tmp)
}

We must take a look here: go2pins/transform/cfg/transform.go:152 +0x10a
Anyway it seems normal since we can’t compute the processing of foo() directly, setting a temporary variables may do the job in a similar way as transform/arithmeticcall.go

  • More than one function call in AssignStmt seems to not work as well, need to take a look on go2pins/transform/cfg/transform.go.
package main

func foo() int {
	return 42
}

func main() {
	a, b := 1, 2
	a = b
}

Replace 1 or 2 or both to function call, it won’t work.

What’s coming next ? (In progress)

We need to handle inner call in a Blackbox functions, including Go library and main package functions.

Links and more

References and interesting documents