Go plan9 compilation series of articles:
- Go plan9 compilation: Getting through the application to the ground floor
- Go plan9 assembly: handwritten assembly
- Go plan9 assembly: talking through the function stack
0. Preface
In the Go plan9 assembly series of articles, function and function stack calls were introduced. Here we continue to look at the memory alignment and recursive call aspects.
1. Memory alignment
Straight to the example:
type temp struct {
a bool
b int16
c []string
}
func main() {
var t = temp{a: true, b: 1, c: []string{}}
((t))
}
Output:
32
Rewrites the temp structure member variable position:
type temp struct {
a bool
c []string
b int16
}
func main() {
var t = temp{a: true, b: 1, c: []string{}}
((t))
}
Output:
40
Why does moving down the position of a structure member have an effect on the size of the structure in memory?
The address of the structure member variable in the print example is as follows:
# Example 1
func main() {
var t = temp{a: true, b: 1, c: []string{}}
((), (), ())
("%p %p %p %p\n", &t, &, &, &, &)
((t))
}
# go run
1 2 24
0xc0000a4000 0xc0000a4000 0xc0000a4002 0xc0000a4008
32
# Example 2
func main() {
var t = temp{a: true, b: 1, c: []string{}}
((), (), ())
("%p %p %p %p %p\n", &t, &, &, &, &)
((t))
}
# go run
1 24 2
0xc00006e090 0xc00006e090 0xc00006e098 0xc00006e0b0
40
As you can see, memory alignment is followed when allocating memory for structures. Memory alignment is used to simplify addressing so that the CPU can find the location of a variable at once. Because of memory alignment, the variable a in Example 2 takes up 8 bytes, even though it only takes up 1 byte, which is a memory drain on the code and should be avoided.
2. Recursion
Let's look at an example of recursion:
func main() {
println(sum(1000))
}
//go:nosplit
func sum(n int) int {
if n > 0 {
return n + sum(n-1)
} else {
return 0
}
}
Output:
# go run
# command-line-arguments
: nosplit stack over 792 byte limit
<1>
grows 24 bytes, calls <1>
infinite cycle
Here we are atsum
prefix//go:nosplit
is to declare that this function is non-stack-splittable. Meaning that when the function stack is full, no new space will be opened (by the memory allocator) for it.
Go allocates 2K of initial stack space for goroutines. If the main stack plus the sum stack of the nosplit exceeds 2K, the stack will explode.
commander-in-chief (military)//go:nosplit
Take it out and re-execute it:
func main() {
println(sum(100000))
}
func sum(n int) int {
if n > 0 {
return n + sum(n-1)
} else {
return 0
}
}
Output:
5000050000
in that casesum
Is it possible to have infinite recursion? We givesum
A large number 10000000000000, then re-execute:
runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0xc0200f8398 stack=[0xc0200f8000, 0xc0400f8000]
fatal error: stack overflow
exportsstack overflow
If the stack of the main program is from 0xc0200f8000 to 0xc0400f8000, the recursive stack exceeds the maximum limit of the goroutine's stack.1000000000-byte
(exceeded means that the stack of the main stack plus the stack of the sum recursive call exceeds the maximum limit), which is 1G.