// 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. | |

// Address range data structure. | |

// | |

// This file contains an implementation of a data structure which | |

// manages ordered address ranges. | |

package runtime | |

import ( | |

"runtime/internal/sys" | |

"unsafe" | |

) | |

// addrRange represents a region of address space. | |

type addrRange struct { | |

// base and limit together represent the region of address space | |

// [base, limit). That is, base is inclusive, limit is exclusive. | |

base, limit uintptr | |

} | |

// size returns the size of the range represented in bytes. | |

func (a addrRange) size() uintptr { | |

if a.limit <= a.base { | |

return 0 | |

} | |

return a.limit - a.base | |

} | |

// contains returns whether or not the range contains a given address. | |

func (a addrRange) contains(addr uintptr) bool { | |

return addr >= a.base && addr < a.limit | |

} | |

// subtract takes the addrRange toPrune and cuts out any overlap with | |

// from, then returns the new range. subtract assumes that a and b | |

// either don't overlap at all, only overlap on one side, or are equal. | |

// If b is strictly contained in a, thus forcing a split, it will throw. | |

func (a addrRange) subtract(b addrRange) addrRange { | |

if a.base >= b.base && a.limit <= b.limit { | |

return addrRange{} | |

} else if a.base < b.base && a.limit > b.limit { | |

throw("bad prune") | |

} else if a.limit > b.limit && a.base < b.limit { | |

a.base = b.limit | |

} else if a.base < b.base && a.limit > b.base { | |

a.limit = b.base | |

} | |

return a | |

} | |

// addrRanges is a data structure holding a collection of ranges of | |

// address space. | |

// | |

// The ranges are coalesced eagerly to reduce the | |

// number ranges it holds. | |

// | |

// The slice backing store for this field is persistentalloc'd | |

// and thus there is no way to free it. | |

// | |

// addrRanges is not thread-safe. | |

type addrRanges struct { | |

// ranges is a slice of ranges sorted by base. | |

ranges []addrRange | |

// sysStat is the stat to track allocations by this type | |

sysStat *uint64 | |

} | |

func (a *addrRanges) init(sysStat *uint64) { | |

ranges := (*notInHeapSlice)(unsafe.Pointer(&a.ranges)) | |

ranges.len = 0 | |

ranges.cap = 16 | |

ranges.array = (*notInHeap)(persistentalloc(unsafe.Sizeof(addrRange{})*uintptr(ranges.cap), sys.PtrSize, sysStat)) | |

a.sysStat = sysStat | |

} | |

// findSucc returns the first index in a such that base is | |

// less than the base of the addrRange at that index. | |

func (a *addrRanges) findSucc(base uintptr) int { | |

// TODO(mknyszek): Consider a binary search for large arrays. | |

// While iterating over these ranges is potentially expensive, | |

// the expected number of ranges is small, ideally just 1, | |

// since Go heaps are usually mostly contiguous. | |

for i := range a.ranges { | |

if base < a.ranges[i].base { | |

return i | |

} | |

} | |

return len(a.ranges) | |

} | |

// contains returns true if a covers the address addr. | |

func (a *addrRanges) contains(addr uintptr) bool { | |

i := a.findSucc(addr) | |

if i == 0 { | |

return false | |

} | |

return a.ranges[i-1].contains(addr) | |

} | |

// add inserts a new address range to a. | |

// | |

// r must not overlap with any address range in a. | |

func (a *addrRanges) add(r addrRange) { | |

// The copies in this function are potentially expensive, but this data | |

// structure is meant to represent the Go heap. At worst, copying this | |

// would take ~160µs assuming a conservative copying rate of 25 GiB/s (the | |

// copy will almost never trigger a page fault) for a 1 TiB heap with 4 MiB | |

// arenas which is completely discontiguous. ~160µs is still a lot, but in | |

// practice most platforms have 64 MiB arenas (which cuts this by a factor | |

// of 16) and Go heaps are usually mostly contiguous, so the chance that | |

// an addrRanges even grows to that size is extremely low. | |

// Because we assume r is not currently represented in a, | |

// findSucc gives us our insertion index. | |

i := a.findSucc(r.base) | |

coalescesDown := i > 0 && a.ranges[i-1].limit == r.base | |

coalescesUp := i < len(a.ranges) && r.limit == a.ranges[i].base | |

if coalescesUp && coalescesDown { | |

// We have neighbors and they both border us. | |

// Merge a.ranges[i-1], r, and a.ranges[i] together into a.ranges[i-1]. | |

a.ranges[i-1].limit = a.ranges[i].limit | |

// Delete a.ranges[i]. | |

copy(a.ranges[i:], a.ranges[i+1:]) | |

a.ranges = a.ranges[:len(a.ranges)-1] | |

} else if coalescesDown { | |

// We have a neighbor at a lower address only and it borders us. | |

// Merge the new space into a.ranges[i-1]. | |

a.ranges[i-1].limit = r.limit | |

} else if coalescesUp { | |

// We have a neighbor at a higher address only and it borders us. | |

// Merge the new space into a.ranges[i]. | |

a.ranges[i].base = r.base | |

} else { | |

// We may or may not have neighbors which don't border us. | |

// Add the new range. | |

if len(a.ranges)+1 > cap(a.ranges) { | |

// Grow the array. Note that this leaks the old array, but since | |

// we're doubling we have at most 2x waste. For a 1 TiB heap and | |

// 4 MiB arenas which are all discontiguous (both very conservative | |

// assumptions), this would waste at most 4 MiB of memory. | |

oldRanges := a.ranges | |

ranges := (*notInHeapSlice)(unsafe.Pointer(&a.ranges)) | |

ranges.len = len(oldRanges) + 1 | |

ranges.cap = cap(oldRanges) * 2 | |

ranges.array = (*notInHeap)(persistentalloc(unsafe.Sizeof(addrRange{})*uintptr(ranges.cap), sys.PtrSize, a.sysStat)) | |

// Copy in the old array, but make space for the new range. | |

copy(a.ranges[:i], oldRanges[:i]) | |

copy(a.ranges[i+1:], oldRanges[i:]) | |

} else { | |

a.ranges = a.ranges[:len(a.ranges)+1] | |

copy(a.ranges[i+1:], a.ranges[i:]) | |

} | |

a.ranges[i] = r | |

} | |

} |