js/xpconnect/loader/AutoMemMap.cpp
author Kris Maglione <maglione.k@gmail.com>
Fri, 29 Jun 2018 22:50:41 -0700
changeset 479670 b982fcdb2ded44272f2c08be9579b59fd2406364
parent 479669 e5bf63c13445d926553f85ed9381ca38d15a0781
child 481425 c27295337b4c16e2a178106a3aa873d2a0e5a1f4
permissions -rw-r--r--
Bug 1470365: Part 1 - Add a compact, read-only, shared-memory string map class. r=erahm This class implements a shared memory key-value store that fits into a single memory mapped segment. All of the runtime data for its instances are stored in the shared memory region, which means that memory overhead for each instance in each process is only a few bytes. Importantly, the key and value strings returned by this class are also pointers into the shared memory region, which means that once an instance is created, its memory cannot be unmapped until process shutdown. For the uses I intend to put it to, this is a reasonable constraint. If we need to use it for shorter-lived maps in the future, we can add an option to return non-literal dependent strings that will be copied if they need to be kept alive long term. MozReview-Commit-ID: 5BwAaDsb7HS

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "AutoMemMap.h"
#include "ScriptPreloader-inl.h"

#include "mozilla/Unused.h"
#include "nsIFile.h"

#include <private/pprio.h>

namespace mozilla {
namespace loader {

using namespace mozilla::ipc;

AutoMemMap::~AutoMemMap()
{
    reset();
}

FileDescriptor
AutoMemMap::cloneFileDescriptor() const
{
    if (fd.get()) {
        auto handle = FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(fd.get()));
        return FileDescriptor(handle);
    }
    return FileDescriptor();
}

Result<Ok, nsresult>
AutoMemMap::init(nsIFile* file, int flags, int mode, PRFileMapProtect prot)
{
    MOZ_ASSERT(!fd);

    MOZ_TRY(file->OpenNSPRFileDesc(flags, mode, &fd.rwget()));

    return initInternal(prot);
}

Result<Ok, nsresult>
AutoMemMap::init(const FileDescriptor& file, PRFileMapProtect prot,
                 size_t expectedSize)
{
    MOZ_ASSERT(!fd);
    if (!file.IsValid()) {
        return Err(NS_ERROR_INVALID_ARG);
    }

    auto handle = file.ClonePlatformHandle();

    fd = PR_ImportFile(PROsfd(handle.get()));
    if (!fd) {
        return Err(NS_ERROR_FAILURE);
    }
    Unused << handle.release();

    return initInternal(prot, expectedSize);
}

Result<Ok, nsresult>
AutoMemMap::initInternal(PRFileMapProtect prot, size_t expectedSize)
{
    MOZ_ASSERT(!fileMap);
    MOZ_ASSERT(!addr);

    PRFileInfo64 fileInfo;
    MOZ_TRY(PR_GetOpenFileInfo64(fd.get(), &fileInfo));

    if (fileInfo.size > UINT32_MAX)
        return Err(NS_ERROR_INVALID_ARG);

    fileMap = PR_CreateFileMap(fd, 0, prot);
    if (!fileMap)
        return Err(NS_ERROR_FAILURE);

    size_ = fileInfo.size;
    // The memory region size passed in certain IPC messages isn't necessary on
    // Unix-like systems, since we can always stat the file descriptor to
    // determine it accurately. But since we have it, anyway, sanity check that
    // it matches the size returned by the stat.
    MOZ_ASSERT_IF(expectedSize > 0, size_ == expectedSize);

    addr = PR_MemMap(fileMap, 0, size_);
    if (!addr)
        return Err(NS_ERROR_FAILURE);

    return Ok();
}

#ifdef XP_WIN

Result<Ok, nsresult>
AutoMemMap::initWithHandle(const FileDescriptor& file, size_t size, PRFileMapProtect prot)
{
    MOZ_ASSERT(!fd);
    MOZ_ASSERT(!handle_);
    if (!file.IsValid()) {
        return Err(NS_ERROR_INVALID_ARG);
    }

    handle_ = file.ClonePlatformHandle().release();

    MOZ_ASSERT(!addr);

    size_ = size;

    addr = MapViewOfFile(
          handle_,
          prot == PR_PROT_READONLY ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS,
          0, 0, size);

    return Ok();
}

FileDescriptor
AutoMemMap::cloneHandle() const
{
    return FileDescriptor(handle_);
}

#else

Result<Ok, nsresult>
AutoMemMap::initWithHandle(const FileDescriptor& file, size_t size, PRFileMapProtect prot)
{
    return init(file, prot);
}

FileDescriptor
AutoMemMap::cloneHandle() const
{
    return cloneFileDescriptor();
}

#endif

void
AutoMemMap::reset()
{
    if (fileMap) {
        if (addr && !persistent_) {
            Unused << NS_WARN_IF(PR_MemUnmap(addr, size()) != PR_SUCCESS);
            addr = nullptr;
        }

        Unused << NS_WARN_IF(PR_CloseFileMap(fileMap) != PR_SUCCESS);
        fileMap = nullptr;
    }
#ifdef XP_WIN
    if (handle_) {
      CloseHandle(handle_);
      handle_ = nullptr;
    }
#endif
    fd.dispose();
}

} // namespace loader
} // namespace mozilla