blob: 6d64f125ace387f78b41740072cb21bb166dd31a [file] [log] [blame]
/*
* Copyright (c) 2017-2020 Advanced Micro Devices, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer;
* redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution;
* neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "sim/mem_state.hh"
#include <cassert>
#include "arch/generic/mmu.hh"
#include "debug/Vma.hh"
#include "mem/se_translating_port_proxy.hh"
#include "sim/process.hh"
#include "sim/syscall_debug_macros.hh"
#include "sim/system.hh"
#include "sim/vma.hh"
MemState::MemState(Process *owner, Addr brk_point, Addr stack_base,
Addr max_stack_size, Addr next_thread_stack_base,
Addr mmap_end)
: _ownerProcess(owner),
_pageBytes(owner->pTable->pageSize()), _brkPoint(brk_point),
_stackBase(stack_base), _stackSize(max_stack_size),
_maxStackSize(max_stack_size), _stackMin(stack_base - max_stack_size),
_nextThreadStackBase(next_thread_stack_base),
_mmapEnd(mmap_end), _endBrkPoint(brk_point)
{
}
MemState&
MemState::operator=(const MemState &in)
{
if (this == &in)
return *this;
_pageBytes = in._pageBytes;
_brkPoint = in._brkPoint;
_stackBase = in._stackBase;
_stackSize = in._stackSize;
_maxStackSize = in._maxStackSize;
_stackMin = in._stackMin;
_nextThreadStackBase = in._nextThreadStackBase;
_mmapEnd = in._mmapEnd;
_endBrkPoint = in._endBrkPoint;
_vmaList = in._vmaList; /* This assignment does a deep copy. */
return *this;
}
void
MemState::resetOwner(Process *owner)
{
_ownerProcess = owner;
}
bool
MemState::isUnmapped(Addr start_addr, Addr length)
{
Addr end_addr = start_addr + length;
const AddrRange range(start_addr, end_addr);
for (const auto &vma : _vmaList) {
if (vma.intersects(range))
return false;
}
/**
* In case someone skips the VMA interface and just directly maps memory
* also consult the page tables to make sure that this memory isnt mapped.
*/
for (auto start = start_addr; start < end_addr;
start += _pageBytes) {
if (_ownerProcess->pTable->lookup(start) != nullptr) {
panic("Someone allocated physical memory at VA %p without "
"creating a VMA!\n", start);
return false;
}
}
return true;
}
void
MemState::updateBrkRegion(Addr old_brk, Addr new_brk)
{
/**
* To make this simple, avoid reducing the heap memory area if the
* new_brk point is less than the old_brk; this occurs when the heap is
* receding because the application has given back memory. The brk point
* is still tracked in the MemState class as an independent field so that
* it can be returned to the application; we just do not update the
* region unless we expand it out.
*/
if (new_brk < old_brk) {
_brkPoint = new_brk;
return;
}
/**
* The regions must be page aligned but the break point can be set on
* byte boundaries. Ensure that the restriction is maintained here by
* extending the request out to the end of the page. (The roundUp
* function will not round up an already aligned page.)
*/
auto page_aligned_brk = roundUp(new_brk, _pageBytes);
/**
* Create a new mapping for the heap region. We only create a mapping
* for the extra memory that is requested so we do not create a situation
* where there can be overlapping mappings in the regions.
*
* Since we do not track the type of the region and we also do not
* coalesce the regions together, we can create a fragmented set of
* heap regions. To resolve this, we keep the furthest point ever mapped
* by the _endBrkPoint field.
*/
if (page_aligned_brk > _endBrkPoint) {
auto length = page_aligned_brk - _endBrkPoint;
/**
* Check if existing mappings impede the expansion of brk expansion.
* If brk cannot expand, it must return the original, unmodified brk
* address and should not modify the mappings here.
*/
if (!isUnmapped(_endBrkPoint, length)) {
return;
}
/**
* Note that the heap regions are always contiguous but there is
* no mechanism right now to coalesce together memory that belongs
* to the same region with similar access permissions. This could be
* implemented if it actually becomes necessary; probably only
* necessary if the list becomes too long to walk.
*/
mapRegion(_endBrkPoint, length, "heap");
_endBrkPoint = page_aligned_brk;
}
_brkPoint = new_brk;
}
void
MemState::mapRegion(Addr start_addr, Addr length,
const std::string& region_name, int sim_fd, Addr offset)
{
DPRINTF(Vma, "memstate: creating vma (%s) [0x%x - 0x%x]\n",
region_name.c_str(), start_addr, start_addr + length);
/**
* Avoid creating a region that has preexisting mappings. This should
* not happen under normal circumstances so consider this to be a bug.
*/
assert(isUnmapped(start_addr, length));
/**
* Record the region in our list structure.
*/
_vmaList.emplace_back(AddrRange(start_addr, start_addr + length),
_pageBytes, region_name, sim_fd, offset);
}
void
MemState::unmapRegion(Addr start_addr, Addr length)
{
Addr end_addr = start_addr + length;
const AddrRange range(start_addr, end_addr);
auto vma = std::begin(_vmaList);
while (vma != std::end(_vmaList)) {
if (vma->isStrictSuperset(range)) {
DPRINTF(Vma, "memstate: split vma [0x%x - 0x%x] into "
"[0x%x - 0x%x] and [0x%x - 0x%x]\n",
vma->start(), vma->end(),
vma->start(), start_addr,
end_addr, vma->end());
/**
* Need to split into two smaller regions.
* Create a clone of the old VMA and slice it to the right.
*/
_vmaList.push_back(*vma);
_vmaList.back().sliceRegionRight(start_addr);
/**
* Slice old VMA to encapsulate the left region.
*/
vma->sliceRegionLeft(end_addr);
/**
* Region cannot be in any more VMA, because it is completely
* contained in this one!
*/
break;
} else if (vma->isSubset(range)) {
DPRINTF(Vma, "memstate: destroying vma [0x%x - 0x%x]\n",
vma->start(), vma->end());
/**
* Need to nuke the existing VMA.
*/
vma = _vmaList.erase(vma);
continue;
} else if (vma->intersects(range)) {
/**
* Trim up the existing VMA.
*/
if (vma->start() < start_addr) {
DPRINTF(Vma, "memstate: resizing vma [0x%x - 0x%x] "
"into [0x%x - 0x%x]\n",
vma->start(), vma->end(),
vma->start(), start_addr);
/**
* Overlaps from the right.
*/
vma->sliceRegionRight(start_addr);
} else {
DPRINTF(Vma, "memstate: resizing vma [0x%x - 0x%x] "
"into [0x%x - 0x%x]\n",
vma->start(), vma->end(),
end_addr, vma->end());
/**
* Overlaps from the left.
*/
vma->sliceRegionLeft(end_addr);
}
}
vma++;
}
/**
* TLBs need to be flushed to remove any stale mappings from regions
* which were unmapped. Currently the entire TLB is flushed. This results
* in functionally correct execution, but real systems do not flush all
* entries when a single mapping changes since it degrades performance.
* There is currently no general method across all TLB implementations
* that can flush just part of the address space.
*/
for (auto *tc: _ownerProcess->system->threads) {
tc->getMMUPtr()->flushAll();
}
do {
if (!_ownerProcess->pTable->isUnmapped(start_addr, _pageBytes))
_ownerProcess->pTable->unmap(start_addr, _pageBytes);
start_addr += _pageBytes;
/**
* The regions need to always be page-aligned otherwise the while
* condition will loop indefinitely. (The Addr type is currently
* defined to be uint64_t in src/base/types.hh; it can underflow
* since it is unsigned.)
*/
length -= _pageBytes;
} while (length > 0);
}
void
MemState::remapRegion(Addr start_addr, Addr new_start_addr, Addr length)
{
Addr end_addr = start_addr + length;
const AddrRange range(start_addr, end_addr);
auto vma = std::begin(_vmaList);
while (vma != std::end(_vmaList)) {
if (vma->isStrictSuperset(range)) {
/**
* Create clone of the old VMA and slice right.
*/
_vmaList.push_back(*vma);
_vmaList.back().sliceRegionRight(start_addr);
/**
* Create clone of the old VMA and slice it left.
*/
_vmaList.push_back(*vma);
_vmaList.back().sliceRegionLeft(end_addr);
/**
* Slice the old VMA left and right to adjust the file backing,
* then overwrite the virtual addresses!
*/
vma->sliceRegionLeft(start_addr);
vma->sliceRegionRight(end_addr);
vma->remap(new_start_addr);
/**
* The region cannot be in any more VMAs, because it is
* completely contained in this one!
*/
break;
} else if (vma->isSubset(range)) {
/**
* Just go ahead and remap it!
*/
vma->remap(vma->start() - start_addr + new_start_addr);
} else if (vma->intersects(range)) {
/**
* Create a clone of the old VMA.
*/
_vmaList.push_back(*vma);
if (vma->start() < start_addr) {
/**
* Overlaps from the right.
*/
_vmaList.back().sliceRegionRight(start_addr);
/**
* Remap the old region.
*/
vma->sliceRegionLeft(start_addr);
vma->remap(new_start_addr);
} else {
/**
* Overlaps from the left.
*/
_vmaList.back().sliceRegionLeft(end_addr);
/**
* Remap the old region.
*/
vma->sliceRegionRight(end_addr);
vma->remap(new_start_addr + vma->start() - start_addr);
}
}
vma++;
}
/**
* TLBs need to be flushed to remove any stale mappings from regions
* which were remapped. Currently the entire TLB is flushed. This results
* in functionally correct execution, but real systems do not flush all
* entries when a single mapping changes since it degrades performance.
* There is currently no general method across all TLB implementations
* that can flush just part of the address space.
*/
for (auto *tc: _ownerProcess->system->threads) {
tc->getMMUPtr()->flushAll();
}
do {
if (!_ownerProcess->pTable->isUnmapped(start_addr, _pageBytes))
_ownerProcess->pTable->remap(start_addr, _pageBytes,
new_start_addr);
start_addr += _pageBytes;
new_start_addr += _pageBytes;
/**
* The regions need to always be page-aligned otherwise the while
* condition will loop indefinitely. (The Addr type is currently
* defined to be uint64_t in src/base/types.hh; it can underflow
* since it is unsigned.)
*/
length -= _pageBytes;
} while (length > 0);
}
bool
MemState::fixupFault(Addr vaddr)
{
/**
* Check if we are accessing a mapped virtual address. If so then we
* just haven't allocated it a physical page yet and can do so here.
*/
for (const auto &vma : _vmaList) {
if (vma.contains(vaddr)) {
Addr vpage_start = roundDown(vaddr, _pageBytes);
_ownerProcess->allocateMem(vpage_start, _pageBytes);
/**
* We are assuming that fresh pages are zero-filled, so there is
* no need to zero them out when there is no backing file.
* This assumption will not hold true if/when physical pages
* are recycled.
*/
if (vma.hasHostBuf()) {
/**
* Write the memory for the host buffer contents for all
* ThreadContexts associated with this process.
*/
for (auto &cid : _ownerProcess->contextIds) {
auto *tc = _ownerProcess->system->threads[cid];
SETranslatingPortProxy
virt_mem(tc, SETranslatingPortProxy::Always);
vma.fillMemPages(vpage_start, _pageBytes, virt_mem);
}
}
return true;
}
}
/**
* Check if the stack needs to be grown in the case where the ISAs
* process argsInit does not explicitly map the entire stack.
*
* Check if this is already on the stack and there's just no page there
* yet.
*/
if (vaddr >= _stackMin && vaddr < _stackBase) {
_ownerProcess->allocateMem(roundDown(vaddr, _pageBytes), _pageBytes);
return true;
}
/**
* We've accessed the next page of the stack, so extend it to include
* this address.
*/
if (vaddr < _stackMin && vaddr >= _stackBase - _maxStackSize) {
while (vaddr < _stackMin) {
_stackMin -= _pageBytes;
if (_stackBase - _stackMin > _maxStackSize) {
fatal("Maximum stack size exceeded\n");
}
_ownerProcess->allocateMem(_stackMin, _pageBytes);
inform("Increasing stack size by one page.");
}
return true;
}
return false;
}
Addr
MemState::extendMmap(Addr length)
{
Addr start = _mmapEnd;
if (_ownerProcess->mmapGrowsDown())
start = _mmapEnd - length;
// Look for a contiguous region of free virtual memory. We can't assume
// that the region beyond mmap_end is free because of fixed mappings from
// the user.
while (!isUnmapped(start, length)) {
DPRINTF(Vma, "memstate: cannot extend vma for mmap region at %p. "
"Virtual address range is already reserved! Skipping a page "
"and trying again!\n", start);
start = (_ownerProcess->mmapGrowsDown()) ? start - _pageBytes :
start + _pageBytes;
}
DPRINTF(Vma, "memstate: extending mmap region (old %p) (new %p)\n",
_mmapEnd,
_ownerProcess->mmapGrowsDown() ? start : start + length);
_mmapEnd = _ownerProcess->mmapGrowsDown() ? start : start + length;
return start;
}
std::string
MemState::printVmaList()
{
std::stringstream file_content;
for (auto vma : _vmaList) {
std::stringstream line;
line << std::hex << vma.start() << "-";
line << std::hex << vma.end() << " ";
line << "r-xp 00000000 00:00 0 ";
line << "[" << vma.getName() << "]" << std::endl;
file_content << line.str();
}
return file_content.str();
}