Commit 39e8cb0f authored by Michael Anthony Knyszek's avatar Michael Anthony Knyszek Committed by Michael Knyszek

runtime: add new page allocator core

This change adds a new bitmap-based allocator to the runtime with tests.
It does not yet integrate the page allocator into the runtime and thus
this change is almost purely additive.

Updates #35112.

Change-Id: Ic3d024c28abee8be8797d3918116a80f901cc2bf
Reviewed-on: https://go-review.googlesource.com/c/go/+/190622
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarAustin Clements <austin@google.com>
parent 05aa4a7b
......@@ -38,6 +38,7 @@ var Nanotime = nanotime
var NetpollBreak = netpollBreak
var Usleep = usleep
var PageSize = pageSize
var PhysHugePageSize = physHugePageSize
var NetpollGenericInit = netpollGenericInit
......@@ -824,7 +825,90 @@ func StringifyPallocBits(b *PallocBits, r BitRange) string {
return str
}
// Expose chunk index type.
type ChunkIdx chunkIdx
// Expose pageAlloc for testing. Note that because pageAlloc is
// not in the heap, so is PageAlloc.
type PageAlloc pageAlloc
func (p *PageAlloc) Alloc(npages uintptr) uintptr { return (*pageAlloc)(p).alloc(npages) }
func (p *PageAlloc) Free(base, npages uintptr) { (*pageAlloc)(p).free(base, npages) }
func (p *PageAlloc) Bounds() (ChunkIdx, ChunkIdx) {
return ChunkIdx((*pageAlloc)(p).start), ChunkIdx((*pageAlloc)(p).end)
}
func (p *PageAlloc) PallocBits(i ChunkIdx) *PallocBits {
return (*PallocBits)(&((*pageAlloc)(p).chunks[i]))
}
// BitRange represents a range over a bitmap.
type BitRange struct {
I, N uint // bit index and length in bits
}
// NewPageAlloc creates a new page allocator for testing and
// initializes it with the chunks map. Each key represents a chunk
// index and each value is a series of bit ranges to set within that
// chunk.
func NewPageAlloc(chunks map[ChunkIdx][]BitRange) *PageAlloc {
p := new(pageAlloc)
// We've got an entry, so initialize the pageAlloc.
p.init(new(mutex), nil)
for i, init := range chunks {
addr := chunkBase(chunkIdx(i))
// Mark the chunk's existence in the pageAlloc.
p.grow(addr, pallocChunkBytes)
// Initialize the bitmap and update pageAlloc metadata.
chunk := &p.chunks[chunkIndex(addr)]
for _, s := range init {
// Ignore the case of s.N == 0. allocRange doesn't handle
// it and it's a no-op anyway.
if s.N != 0 {
chunk.allocRange(s.I, s.N)
}
}
// Update heap metadata for the allocRange calls above.
p.update(addr, pallocChunkPages, false, false)
}
return (*PageAlloc)(p)
}
// FreePageAlloc releases hard OS resources owned by the pageAlloc. Once this
// is called the pageAlloc may no longer be used. The object itself will be
// collected by the garbage collector once it is no longer live.
func FreePageAlloc(pp *PageAlloc) {
p := (*pageAlloc)(pp)
// Free all the mapped space for the summary levels.
if pageAlloc64Bit != 0 {
for l := 0; l < summaryLevels; l++ {
sysFree(unsafe.Pointer(&p.summary[l][0]), uintptr(cap(p.summary[l]))*pallocSumBytes, nil)
}
} else {
resSize := uintptr(0)
for _, s := range p.summary {
resSize += uintptr(cap(s)) * pallocSumBytes
}
sysFree(unsafe.Pointer(&p.summary[0][0]), alignUp(resSize, physPageSize), nil)
}
// Free the mapped space for chunks.
chunksLen := uintptr(cap(p.chunks)) * unsafe.Sizeof(p.chunks[0])
sysFree(unsafe.Pointer(&p.chunks[0]), alignUp(chunksLen, physPageSize), nil)
}
// BaseChunkIdx is a convenient chunkIdx value which works on both
// 64 bit and 32 bit platforms, allowing the tests to share code
// between the two.
var BaseChunkIdx = ChunkIdx(chunkIndex((0xc000*pageAlloc64Bit + 0x200*pageAlloc32Bit) * pallocChunkBytes))
// PageBase returns an address given a chunk index and a page index
// relative to that chunk.
func PageBase(c ChunkIdx, pageIdx uint) uintptr {
return chunkBase(chunkIdx(c)) + uintptr(pageIdx)*pageSize
}
This diff is collapsed.
......@@ -17,7 +17,93 @@
package runtime
import "unsafe"
const (
// The number of levels in the radix tree.
summaryLevels = 4
// Constants for testing.
pageAlloc32Bit = 1
pageAlloc64Bit = 0
)
// See comment in mpagealloc_64bit.go.
var levelBits = [summaryLevels]uint{
summaryL0Bits,
summaryLevelBits,
summaryLevelBits,
summaryLevelBits,
}
// See comment in mpagealloc_64bit.go.
var levelShift = [summaryLevels]uint{
heapAddrBits - summaryL0Bits,
heapAddrBits - summaryL0Bits - 1*summaryLevelBits,
heapAddrBits - summaryL0Bits - 2*summaryLevelBits,
heapAddrBits - summaryL0Bits - 3*summaryLevelBits,
}
// See comment in mpagealloc_64bit.go.
var levelLogPages = [summaryLevels]uint{
logPallocChunkPages + 3*summaryLevelBits,
logPallocChunkPages + 2*summaryLevelBits,
logPallocChunkPages + 1*summaryLevelBits,
logPallocChunkPages,
}
// See mpagealloc_64bit.go for details.
func (s *pageAlloc) sysInit() {
// Calculate how much memory all our entries will take up.
//
// This should be around 12 KiB or less.
totalSize := uintptr(0)
for l := 0; l < summaryLevels; l++ {
totalSize += (uintptr(1) << (heapAddrBits - levelShift[l])) * pallocSumBytes
}
totalSize = alignUp(totalSize, physPageSize)
// Reserve memory for all levels in one go. There shouldn't be much for 32-bit.
reservation := sysReserve(nil, totalSize)
if reservation == nil {
throw("failed to reserve page summary memory")
}
// There isn't much. Just map it and mark it as used immediately.
sysMap(reservation, totalSize, s.sysStat)
sysUsed(reservation, totalSize)
// Iterate over the reservation and cut it up into slices.
//
// Maintain i as the byte offset from reservation where
// the new slice should start.
for l, shift := range levelShift {
entries := 1 << (heapAddrBits - shift)
// Put this reservation into a slice.
sl := notInHeapSlice{(*notInHeap)(reservation), 0, entries}
s.summary[l] = *(*[]pallocSum)(unsafe.Pointer(&sl))
reservation = add(reservation, uintptr(entries)*pallocSumBytes)
}
}
// See mpagealloc_64bit.go for details.
func (s *pageAlloc) sysGrow(base, limit uintptr) {
if base%pallocChunkBytes != 0 || limit%pallocChunkBytes != 0 {
print("runtime: base = ", hex(base), ", limit = ", hex(limit), "\n")
throw("sysGrow bounds not aligned to pallocChunkBytes")
}
// Walk up the tree and update the summary slices.
for l := len(s.summary) - 1; l >= 0; l-- {
// Figure out what part of the summary array this new address space needs.
// Note that we need to align the ranges to the block width (1<<levelBits[l])
// at this level because the full block is needed to compute the summary for
// the next level.
lo, hi := addrsToSummaryRange(l, base, limit)
_, hi = blockAlignSummaryRange(l, lo, hi)
if hi > len(s.summary[l]) {
s.summary[l] = s.summary[l][:hi]
}
}
}
......@@ -8,7 +8,129 @@
package runtime
import "unsafe"
const (
// The number of levels in the radix tree.
summaryLevels = 5
// Constants for testing.
pageAlloc32Bit = 0
pageAlloc64Bit = 1
)
// levelBits is the number of bits in the radix for a given level in the super summary
// structure.
//
// The sum of all the entries of levelBits should equal heapAddrBits.
var levelBits = [summaryLevels]uint{
summaryL0Bits,
summaryLevelBits,
summaryLevelBits,
summaryLevelBits,
summaryLevelBits,
}
// levelShift is the number of bits to shift to acquire the radix for a given level
// in the super summary structure.
//
// With levelShift, one can compute the index of the summary at level l related to a
// pointer p by doing:
// p >> levelShift[l]
var levelShift = [summaryLevels]uint{
heapAddrBits - summaryL0Bits,
heapAddrBits - summaryL0Bits - 1*summaryLevelBits,
heapAddrBits - summaryL0Bits - 2*summaryLevelBits,
heapAddrBits - summaryL0Bits - 3*summaryLevelBits,
heapAddrBits - summaryL0Bits - 4*summaryLevelBits,
}
// levelLogPages is log2 the maximum number of runtime pages in the address space
// a summary in the given level represents.
//
// The leaf level always represents exactly log2 of 1 chunk's worth of pages.
var levelLogPages = [summaryLevels]uint{
logPallocChunkPages + 4*summaryLevelBits,
logPallocChunkPages + 3*summaryLevelBits,
logPallocChunkPages + 2*summaryLevelBits,
logPallocChunkPages + 1*summaryLevelBits,
logPallocChunkPages,
}
// sysInit performs architecture-dependent initialization of fields
// in pageAlloc. pageAlloc should be uninitialized except for sysStat
// if any runtime statistic should be updated.
func (s *pageAlloc) sysInit() {
// Reserve memory for each level. This will get mapped in
// as R/W by setArenas.
for l, shift := range levelShift {
entries := 1 << (heapAddrBits - shift)
// Reserve b bytes of memory anywhere in the address space.
b := alignUp(uintptr(entries)*pallocSumBytes, physPageSize)
r := sysReserve(nil, b)
if r == nil {
throw("failed to reserve page summary memory")
}
// Put this reservation into a slice.
sl := notInHeapSlice{(*notInHeap)(r), 0, entries}
s.summary[l] = *(*[]pallocSum)(unsafe.Pointer(&sl))
}
}
// sysGrow performs architecture-dependent operations on heap
// growth for the page allocator, such as mapping in new memory
// for summaries. It also updates the length of the slices in
// s.summary.
//
// base is the base of the newly-added heap memory and limit is
// the first address past the end of the newly-added heap memory.
// Both must be aligned to pallocChunkBytes.
//
// The caller must update s.start and s.end after calling sysGrow.
func (s *pageAlloc) sysGrow(base, limit uintptr) {
if base%pallocChunkBytes != 0 || limit%pallocChunkBytes != 0 {
print("runtime: base = ", hex(base), ", limit = ", hex(limit), "\n")
throw("sysGrow bounds not aligned to pallocChunkBytes")
}
// Walk up the radix tree and map summaries in as needed.
cbase, climit := chunkBase(s.start), chunkBase(s.end)
for l := len(s.summary) - 1; l >= 0; l-- {
// Figure out what part of the summary array this new address space needs.
// Note that we need to align the ranges to the block width (1<<levelBits[l])
// at this level because the full block is needed to compute the summary for
// the next level.
lo, hi := addrsToSummaryRange(l, base, limit)
lo, hi = blockAlignSummaryRange(l, lo, hi)
// Update the summary slices with a new upper-bound. This ensures
// we get tight bounds checks on at least the top bound.
//
// We must do this regardless of whether we map new memory, because we
// may be extending further into the mapped memory.
if hi > len(s.summary[l]) {
s.summary[l] = s.summary[l][:hi]
}
// Figure out what part of the summary array is already mapped.
// If we're doing our first growth, just pass zero.
// addrsToSummaryRange won't accept cbase == climit.
var mlo, mhi int
if s.start != 0 {
mlo, mhi = addrsToSummaryRange(l, cbase, climit)
mlo, mhi = blockAlignSummaryRange(l, mlo, mhi)
}
// Extend the mappings for this summary level.
extendMappedRegion(
unsafe.Pointer(&s.summary[l][0]),
uintptr(mlo)*pallocSumBytes,
uintptr(mhi)*pallocSumBytes,
uintptr(lo)*pallocSumBytes,
uintptr(hi)*pallocSumBytes,
s.sysStat,
)
}
}
This diff is collapsed.
......@@ -13,7 +13,7 @@ import (
// Ensures that got and want are the same, and if not, reports
// detailed diff information.
func checkPallocBits(t *testing.T, got, want *PallocBits) {
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))
......@@ -22,7 +22,9 @@ func checkPallocBits(t *testing.T, got, want *PallocBits) {
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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment