// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pagetables

// Allocator is used to allocate and map PTEs.
//
// Note that allocators may be called concurrently.
type Allocator interface {
	// NewPTEs returns a new set of PTEs and their physical address.
	NewPTEs() *PTEs

	// PhysicalFor gives the physical address for a set of PTEs.
	PhysicalFor(ptes *PTEs) uintptr

	// LookupPTEs looks up PTEs by physical address.
	LookupPTEs(physical uintptr) *PTEs

	// FreePTEs marks a set of PTEs a freed, although they may not be available
	// for use again until Recycle is called, below.
	FreePTEs(ptes *PTEs)

	// Recycle makes freed PTEs available for use again.
	Recycle()
}

// RuntimeAllocator is a trivial allocator.
type RuntimeAllocator struct {
	// used is the set of PTEs that have been allocated. This includes any
	// PTEs that may be in the pool below. PTEs are only freed from this
	// map by the Drain call.
	//
	// This exists to prevent accidental garbage collection.
	used map[*PTEs]struct{}

	// pool is the set of free-to-use PTEs.
	pool []*PTEs

	// freed is the set of recently-freed PTEs.
	freed []*PTEs
}

// NewRuntimeAllocator returns an allocator that uses runtime allocation.
func NewRuntimeAllocator() *RuntimeAllocator {
	return &RuntimeAllocator{
		used: make(map[*PTEs]struct{}),
	}
}

// Recycle returns freed pages to the pool.
func (r *RuntimeAllocator) Recycle() {
	r.pool = append(r.pool, r.freed...)
	r.freed = r.freed[:0]
}

// Drain empties the pool.
func (r *RuntimeAllocator) Drain() {
	r.Recycle()
	for i, ptes := range r.pool {
		// Zap the entry in the underlying array to ensure that it can
		// be properly garbage collected.
		r.pool[i] = nil
		// Similarly, free the reference held by the used map (these
		// also apply for the pool entries).
		delete(r.used, ptes)
	}
	r.pool = r.pool[:0]
}

// NewPTEs implements Allocator.NewPTEs.
//
// Note that the "physical" address here is actually the virtual address of the
// PTEs structure. The entries are tracked only to avoid garbage collection.
//
// This is guaranteed not to split as long as the pool is sufficiently full.
//
//go:nosplit
func (r *RuntimeAllocator) NewPTEs() *PTEs {
	// Pull from the pool if we can.
	if len(r.pool) > 0 {
		ptes := r.pool[len(r.pool)-1]
		r.pool = r.pool[:len(r.pool)-1]
		return ptes
	}

	// Allocate a new entry.
	ptes := newAlignedPTEs()
	r.used[ptes] = struct{}{}
	return ptes
}

// PhysicalFor returns the physical address for the given PTEs.
//
//go:nosplit
func (r *RuntimeAllocator) PhysicalFor(ptes *PTEs) uintptr {
	return physicalFor(ptes)
}

// LookupPTEs implements Allocator.LookupPTEs.
//
//go:nosplit
func (r *RuntimeAllocator) LookupPTEs(physical uintptr) *PTEs {
	return fromPhysical(physical)
}

// FreePTEs implements Allocator.FreePTEs.
//
//go:nosplit
func (r *RuntimeAllocator) FreePTEs(ptes *PTEs) {
	r.freed = append(r.freed, ptes)
}