blob: 71a29f3b3a2e55895762078a3df20bce483a4f48 [file] [log] [blame] [edit]
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime_test
import (
"fmt"
"math/rand"
. "runtime"
"testing"
)
// Ensures that got and want are the same, and if not, reports
// detailed diff information.
func checkPallocBits(t *testing.T, got, want *PallocBits) bool {
d := DiffPallocBits(got, want)
if len(d) != 0 {
t.Errorf("%d range(s) different", len(d))
for _, bits := range d {
t.Logf("\t@ bit index %d", bits.I)
t.Logf("\t| got: %s", StringifyPallocBits(got, bits))
t.Logf("\t| want: %s", StringifyPallocBits(want, bits))
}
return false
}
return true
}
// makePallocBits produces an initialized PallocBits by setting
// the ranges in s to 1 and the rest to zero.
func makePallocBits(s []BitRange) *PallocBits {
b := new(PallocBits)
for _, v := range s {
b.AllocRange(v.I, v.N)
}
return b
}
// Ensures that PallocBits.AllocRange works, which is a fundamental
// method used for testing and initialization since it's used by
// makePallocBits.
func TestPallocBitsAllocRange(t *testing.T) {
test := func(t *testing.T, i, n uint, want *PallocBits) {
checkPallocBits(t, makePallocBits([]BitRange{{i, n}}), want)
}
t.Run("OneLow", func(t *testing.T) {
want := new(PallocBits)
want[0] = 0x1
test(t, 0, 1, want)
})
t.Run("OneHigh", func(t *testing.T) {
want := new(PallocBits)
want[PallocChunkPages/64-1] = 1 << 63
test(t, PallocChunkPages-1, 1, want)
})
t.Run("Inner", func(t *testing.T) {
want := new(PallocBits)
want[2] = 0x3e
test(t, 129, 5, want)
})
t.Run("Aligned", func(t *testing.T) {
want := new(PallocBits)
want[2] = ^uint64(0)
want[3] = ^uint64(0)
test(t, 128, 128, want)
})
t.Run("Begin", func(t *testing.T) {
want := new(PallocBits)
want[0] = ^uint64(0)
want[1] = ^uint64(0)
want[2] = ^uint64(0)
want[3] = ^uint64(0)
want[4] = ^uint64(0)
want[5] = 0x1
test(t, 0, 321, want)
})
t.Run("End", func(t *testing.T) {
want := new(PallocBits)
want[PallocChunkPages/64-1] = ^uint64(0)
want[PallocChunkPages/64-2] = ^uint64(0)
want[PallocChunkPages/64-3] = ^uint64(0)
want[PallocChunkPages/64-4] = 1 << 63
test(t, PallocChunkPages-(64*3+1), 64*3+1, want)
})
t.Run("All", func(t *testing.T) {
want := new(PallocBits)
for i := range want {
want[i] = ^uint64(0)
}
test(t, 0, PallocChunkPages, want)
})
}
// Inverts every bit in the PallocBits.
func invertPallocBits(b *PallocBits) {
for i := range b {
b[i] = ^b[i]
}
}
// Ensures two packed summaries are identical, and reports a detailed description
// of the difference if they're not.
func checkPallocSum(t *testing.T, got, want PallocSum) {
if got.Start() != want.Start() {
t.Errorf("inconsistent start: got %d, want %d", got.Start(), want.Start())
}
if got.Max() != want.Max() {
t.Errorf("inconsistent max: got %d, want %d", got.Max(), want.Max())
}
if got.End() != want.End() {
t.Errorf("inconsistent end: got %d, want %d", got.End(), want.End())
}
}
func TestMallocBitsPopcntRange(t *testing.T) {
type test struct {
i, n uint // bit range to popcnt over.
want uint // expected popcnt result on that range.
}
tests := map[string]struct {
init []BitRange // bit ranges to set to 1 in the bitmap.
tests []test // a set of popcnt tests to run over the bitmap.
}{
"None": {
tests: []test{
{0, 1, 0},
{5, 3, 0},
{2, 11, 0},
{PallocChunkPages/4 + 1, PallocChunkPages / 2, 0},
{0, PallocChunkPages, 0},
},
},
"All": {
init: []BitRange{{0, PallocChunkPages}},
tests: []test{
{0, 1, 1},
{5, 3, 3},
{2, 11, 11},
{PallocChunkPages/4 + 1, PallocChunkPages / 2, PallocChunkPages / 2},
{0, PallocChunkPages, PallocChunkPages},
},
},
"Half": {
init: []BitRange{{PallocChunkPages / 2, PallocChunkPages / 2}},
tests: []test{
{0, 1, 0},
{5, 3, 0},
{2, 11, 0},
{PallocChunkPages/2 - 1, 1, 0},
{PallocChunkPages / 2, 1, 1},
{PallocChunkPages/2 + 10, 1, 1},
{PallocChunkPages/2 - 1, 2, 1},
{PallocChunkPages / 4, PallocChunkPages / 4, 0},
{PallocChunkPages / 4, PallocChunkPages/4 + 1, 1},
{PallocChunkPages/4 + 1, PallocChunkPages / 2, PallocChunkPages/4 + 1},
{0, PallocChunkPages, PallocChunkPages / 2},
},
},
"OddBound": {
init: []BitRange{{0, 111}},
tests: []test{
{0, 1, 1},
{5, 3, 3},
{2, 11, 11},
{110, 2, 1},
{99, 50, 12},
{110, 1, 1},
{111, 1, 0},
{99, 1, 1},
{120, 1, 0},
{PallocChunkPages / 2, PallocChunkPages / 2, 0},
{0, PallocChunkPages, 111},
},
},
"Scattered": {
init: []BitRange{
{1, 3}, {5, 1}, {7, 1}, {10, 2}, {13, 1}, {15, 4},
{21, 1}, {23, 1}, {26, 2}, {30, 5}, {36, 2}, {40, 3},
{44, 6}, {51, 1}, {53, 2}, {58, 3}, {63, 1}, {67, 2},
{71, 10}, {84, 1}, {89, 7}, {99, 2}, {103, 1}, {107, 2},
{111, 1}, {113, 1}, {115, 1}, {118, 1}, {120, 2}, {125, 5},
},
tests: []test{
{0, 11, 6},
{0, 64, 39},
{13, 64, 40},
{64, 64, 34},
{0, 128, 73},
{1, 128, 74},
{0, PallocChunkPages, 75},
},
},
}
for name, v := range tests {
v := v
t.Run(name, func(t *testing.T) {
b := makePallocBits(v.init)
for _, h := range v.tests {
if got := b.PopcntRange(h.i, h.n); got != h.want {
t.Errorf("bad popcnt (i=%d, n=%d): got %d, want %d", h.i, h.n, got, h.want)
}
}
})
}
}
// Ensures computing bit summaries works as expected by generating random
// bitmaps and checking against a reference implementation.
func TestPallocBitsSummarizeRandom(t *testing.T) {
b := new(PallocBits)
for i := 0; i < 1000; i++ {
// Randomize bitmap.
for i := range b {
b[i] = rand.Uint64()
}
// Check summary against reference implementation.
checkPallocSum(t, b.Summarize(), SummarizeSlow(b))
}
}
// Ensures computing bit summaries works as expected.
func TestPallocBitsSummarize(t *testing.T) {
var emptySum = PackPallocSum(PallocChunkPages, PallocChunkPages, PallocChunkPages)
type test struct {
free []BitRange // Ranges of free (zero) bits.
hits []PallocSum
}
tests := make(map[string]test)
tests["NoneFree"] = test{
free: []BitRange{},
hits: []PallocSum{
PackPallocSum(0, 0, 0),
},
}
tests["OnlyStart"] = test{
free: []BitRange{{0, 10}},
hits: []PallocSum{
PackPallocSum(10, 10, 0),
},
}
tests["OnlyEnd"] = test{
free: []BitRange{{PallocChunkPages - 40, 40}},
hits: []PallocSum{
PackPallocSum(0, 40, 40),
},
}
tests["StartAndEnd"] = test{
free: []BitRange{{0, 11}, {PallocChunkPages - 23, 23}},
hits: []PallocSum{
PackPallocSum(11, 23, 23),
},
}
tests["StartMaxEnd"] = test{
free: []BitRange{{0, 4}, {50, 100}, {PallocChunkPages - 4, 4}},
hits: []PallocSum{
PackPallocSum(4, 100, 4),
},
}
tests["OnlyMax"] = test{
free: []BitRange{{1, 20}, {35, 241}, {PallocChunkPages - 50, 30}},
hits: []PallocSum{
PackPallocSum(0, 241, 0),
},
}
tests["MultiMax"] = test{
free: []BitRange{{35, 2}, {40, 5}, {100, 5}},
hits: []PallocSum{
PackPallocSum(0, 5, 0),
},
}
tests["One"] = test{
free: []BitRange{{2, 1}},
hits: []PallocSum{
PackPallocSum(0, 1, 0),
},
}
tests["AllFree"] = test{
free: []BitRange{{0, PallocChunkPages}},
hits: []PallocSum{
emptySum,
},
}
for name, v := range tests {
v := v
t.Run(name, func(t *testing.T) {
b := makePallocBits(v.free)
// In the PallocBits we create 1's represent free spots, but in our actual
// PallocBits 1 means not free, so invert.
invertPallocBits(b)
for _, h := range v.hits {
checkPallocSum(t, b.Summarize(), h)
}
})
}
}
// Benchmarks how quickly we can summarize a PallocBits.
func BenchmarkPallocBitsSummarize(b *testing.B) {
buf0 := new(PallocBits)
buf1 := new(PallocBits)
for i := 0; i < len(buf1); i++ {
buf1[i] = ^uint64(0)
}
bufa := new(PallocBits)
for i := 0; i < len(bufa); i++ {
bufa[i] = 0xaa
}
for _, buf := range []*PallocBits{buf0, buf1, bufa} {
b.Run(fmt.Sprintf("Unpacked%02X", buf[0]), func(b *testing.B) {
for i := 0; i < b.N; i++ {
buf.Summarize()
}
})
}
}
// Ensures page allocation works.
func TestPallocBitsAlloc(t *testing.T) {
tests := map[string]struct {
before []BitRange
after []BitRange
npages uintptr
hits []uint
}{
"AllFree1": {
npages: 1,
hits: []uint{0, 1, 2, 3, 4, 5},
after: []BitRange{{0, 6}},
},
"AllFree2": {
npages: 2,
hits: []uint{0, 2, 4, 6, 8, 10},
after: []BitRange{{0, 12}},
},
"AllFree5": {
npages: 5,
hits: []uint{0, 5, 10, 15, 20},
after: []BitRange{{0, 25}},
},
"AllFree64": {
npages: 64,
hits: []uint{0, 64, 128},
after: []BitRange{{0, 192}},
},
"AllFree65": {
npages: 65,
hits: []uint{0, 65, 130},
after: []BitRange{{0, 195}},
},
"SomeFree64": {
before: []BitRange{{0, 32}, {64, 32}, {100, PallocChunkPages - 100}},
npages: 64,
hits: []uint{^uint(0)},
after: []BitRange{{0, 32}, {64, 32}, {100, PallocChunkPages - 100}},
},
"NoneFree1": {
before: []BitRange{{0, PallocChunkPages}},
npages: 1,
hits: []uint{^uint(0), ^uint(0)},
after: []BitRange{{0, PallocChunkPages}},
},
"NoneFree2": {
before: []BitRange{{0, PallocChunkPages}},
npages: 2,
hits: []uint{^uint(0), ^uint(0)},
after: []BitRange{{0, PallocChunkPages}},
},
"NoneFree5": {
before: []BitRange{{0, PallocChunkPages}},
npages: 5,
hits: []uint{^uint(0), ^uint(0)},
after: []BitRange{{0, PallocChunkPages}},
},
"NoneFree65": {
before: []BitRange{{0, PallocChunkPages}},
npages: 65,
hits: []uint{^uint(0), ^uint(0)},
after: []BitRange{{0, PallocChunkPages}},
},
"ExactFit1": {
before: []BitRange{{0, PallocChunkPages/2 - 3}, {PallocChunkPages/2 - 2, PallocChunkPages/2 + 2}},
npages: 1,
hits: []uint{PallocChunkPages/2 - 3, ^uint(0)},
after: []BitRange{{0, PallocChunkPages}},
},
"ExactFit2": {
before: []BitRange{{0, PallocChunkPages/2 - 3}, {PallocChunkPages/2 - 1, PallocChunkPages/2 + 1}},
npages: 2,
hits: []uint{PallocChunkPages/2 - 3, ^uint(0)},
after: []BitRange{{0, PallocChunkPages}},
},
"ExactFit5": {
before: []BitRange{{0, PallocChunkPages/2 - 3}, {PallocChunkPages/2 + 2, PallocChunkPages/2 - 2}},
npages: 5,
hits: []uint{PallocChunkPages/2 - 3, ^uint(0)},
after: []BitRange{{0, PallocChunkPages}},
},
"ExactFit65": {
before: []BitRange{{0, PallocChunkPages/2 - 31}, {PallocChunkPages/2 + 34, PallocChunkPages/2 - 34}},
npages: 65,
hits: []uint{PallocChunkPages/2 - 31, ^uint(0)},
after: []BitRange{{0, PallocChunkPages}},
},
"SomeFree161": {
before: []BitRange{{0, 185}, {331, 1}},
npages: 161,
hits: []uint{332},
after: []BitRange{{0, 185}, {331, 162}},
},
}
for name, v := range tests {
v := v
t.Run(name, func(t *testing.T) {
b := makePallocBits(v.before)
for iter, i := range v.hits {
a, _ := b.Find(v.npages, 0)
if i != a {
t.Fatalf("find #%d picked wrong index: want %d, got %d", iter+1, i, a)
}
if i != ^uint(0) {
b.AllocRange(a, uint(v.npages))
}
}
want := makePallocBits(v.after)
checkPallocBits(t, b, want)
})
}
}
// Ensures page freeing works.
func TestPallocBitsFree(t *testing.T) {
tests := map[string]struct {
beforeInv []BitRange
afterInv []BitRange
frees []uint
npages uintptr
}{
"SomeFree": {
npages: 1,
beforeInv: []BitRange{{0, 32}, {64, 32}, {100, 1}},
frees: []uint{32},
afterInv: []BitRange{{0, 33}, {64, 32}, {100, 1}},
},
"NoneFree1": {
npages: 1,
frees: []uint{0, 1, 2, 3, 4, 5},
afterInv: []BitRange{{0, 6}},
},
"NoneFree2": {
npages: 2,
frees: []uint{0, 2, 4, 6, 8, 10},
afterInv: []BitRange{{0, 12}},
},
"NoneFree5": {
npages: 5,
frees: []uint{0, 5, 10, 15, 20},
afterInv: []BitRange{{0, 25}},
},
"NoneFree64": {
npages: 64,
frees: []uint{0, 64, 128},
afterInv: []BitRange{{0, 192}},
},
"NoneFree65": {
npages: 65,
frees: []uint{0, 65, 130},
afterInv: []BitRange{{0, 195}},
},
}
for name, v := range tests {
v := v
t.Run(name, func(t *testing.T) {
b := makePallocBits(v.beforeInv)
invertPallocBits(b)
for _, i := range v.frees {
b.Free(i, uint(v.npages))
}
want := makePallocBits(v.afterInv)
invertPallocBits(want)
checkPallocBits(t, b, want)
})
}
}
func TestFindBitRange64(t *testing.T) {
check := func(x uint64, n uint, result uint) {
i := FindBitRange64(x, n)
if result == ^uint(0) && i < 64 {
t.Errorf("case (%016x, %d): got %d, want failure", x, n, i)
} else if result != ^uint(0) && i != result {
t.Errorf("case (%016x, %d): got %d, want %d", x, n, i, result)
}
}
for i := uint(0); i <= 64; i++ {
check(^uint64(0), i, 0)
}
check(0, 0, 0)
for i := uint(1); i <= 64; i++ {
check(0, i, ^uint(0))
}
check(0x8000000000000000, 1, 63)
check(0xc000010001010000, 2, 62)
check(0xc000010001030000, 2, 16)
check(0xe000030001030000, 3, 61)
check(0xe000030001070000, 3, 16)
check(0xffff03ff01070000, 16, 48)
check(0xffff03ff0107ffff, 16, 0)
check(0x0fff03ff01079fff, 16, ^uint(0))
}