luau/CodeGen/src/SharedCodeAllocator.cpp

294 lines
8 KiB
C++
Raw Normal View History

2024-03-22 17:21:27 +00:00
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/SharedCodeAllocator.h"
#include <algorithm>
#include <string_view>
2024-03-30 22:49:03 +00:00
#include <utility>
2024-03-22 17:21:27 +00:00
namespace Luau
{
namespace CodeGen
{
NativeProto::NativeProto(uint32_t bytecodeId, NativeProtoExecDataPtr nativeExecData)
: bytecodeId{bytecodeId}
, nativeExecData{std::move(nativeExecData)}
{
}
void NativeProto::setEntryOffset(uint32_t entryOffset) noexcept
{
entryOffsetOrAddress = reinterpret_cast<const uint8_t*>(static_cast<uintptr_t>(entryOffset));
}
void NativeProto::assignToModule(NativeModule* nativeModule) noexcept
{
getNativeProtoExecDataHeader(nativeExecData.get()).nativeModule = nativeModule;
entryOffsetOrAddress = nativeModule->getModuleBaseAddress() + reinterpret_cast<uintptr_t>(entryOffsetOrAddress);
}
[[nodiscard]] uint32_t NativeProto::getBytecodeId() const noexcept
{
return bytecodeId;
}
[[nodiscard]] const uint8_t* NativeProto::getEntryAddress() const noexcept
{
return entryOffsetOrAddress;
}
[[nodiscard]] const NativeProtoExecDataHeader& NativeProto::getNativeExecDataHeader() const noexcept
{
return getNativeProtoExecDataHeader(nativeExecData.get());
}
[[nodiscard]] const uint32_t* NativeProto::getNonOwningPointerToInstructionOffsets() const noexcept
{
return nativeExecData.get();
}
[[nodiscard]] const uint32_t* NativeProto::getOwningPointerToInstructionOffsets() const noexcept
{
getNativeProtoExecDataHeader(nativeExecData.get()).nativeModule->addRef();
return nativeExecData.get();
}
void NativeProto::releaseOwningPointerToInstructionOffsets(const uint32_t* ownedInstructionOffsets) noexcept
{
getNativeProtoExecDataHeader(ownedInstructionOffsets).nativeModule->release();
}
struct NativeProtoBytecodeIdEqual
{
[[nodiscard]] bool operator()(const NativeProto& left, const NativeProto& right) const noexcept
{
return left.getBytecodeId() == right.getBytecodeId();
}
};
struct NativeProtoBytecodeIdLess
{
[[nodiscard]] bool operator()(const NativeProto& left, const NativeProto& right) const noexcept
{
return left.getBytecodeId() < right.getBytecodeId();
}
[[nodiscard]] bool operator()(const NativeProto& left, uint32_t right) const noexcept
{
return left.getBytecodeId() < right;
}
[[nodiscard]] bool operator()(uint32_t left, const NativeProto& right) const noexcept
{
return left < right.getBytecodeId();
}
};
NativeModule::NativeModule(
SharedCodeAllocator* allocator, const ModuleId& moduleId, const uint8_t* moduleBaseAddress, std::vector<NativeProto> nativeProtos) noexcept
: allocator{allocator}
, moduleId{moduleId}
, moduleBaseAddress{moduleBaseAddress}
, nativeProtos{std::move(nativeProtos)}
{
LUAU_ASSERT(allocator != nullptr);
LUAU_ASSERT(moduleBaseAddress != nullptr);
// Bind all of the NativeProtos to this module:
for (NativeProto& nativeProto : this->nativeProtos)
{
nativeProto.assignToModule(this);
}
std::sort(this->nativeProtos.begin(), this->nativeProtos.end(), NativeProtoBytecodeIdLess{});
// We should not have two NativeProtos for the same bytecode id:
LUAU_ASSERT(std::adjacent_find(this->nativeProtos.begin(), this->nativeProtos.end(), NativeProtoBytecodeIdEqual{}) == this->nativeProtos.end());
}
NativeModule::~NativeModule() noexcept
{
LUAU_ASSERT(refcount == 0);
}
size_t NativeModule::addRef() const noexcept
{
return refcount.fetch_add(1) + 1;
}
size_t NativeModule::release() const noexcept
{
size_t newRefcount = refcount.fetch_sub(1) - 1;
if (newRefcount != 0)
return newRefcount;
allocator->eraseNativeModuleIfUnreferenced(moduleId);
// NOTE: *this may have been destroyed by the prior call, and must not be
// accessed after this point.
return 0;
}
[[nodiscard]] size_t NativeModule::getRefcount() const noexcept
{
return refcount;
}
[[nodiscard]] const uint8_t* NativeModule::getModuleBaseAddress() const noexcept
{
return moduleBaseAddress;
}
[[nodiscard]] const NativeProto* NativeModule::tryGetNativeProto(uint32_t bytecodeId) const noexcept
{
const auto range = std::equal_range(nativeProtos.begin(), nativeProtos.end(), bytecodeId, NativeProtoBytecodeIdLess{});
if (range.first == range.second)
return nullptr;
LUAU_ASSERT(std::next(range.first) == range.second);
return &*range.first;
}
NativeModuleRef::NativeModuleRef(NativeModule* nativeModule) noexcept
: nativeModule{nativeModule}
{
if (nativeModule != nullptr)
nativeModule->addRef();
}
NativeModuleRef::NativeModuleRef(const NativeModuleRef& other) noexcept
: nativeModule{other.nativeModule}
{
if (nativeModule != nullptr)
nativeModule->addRef();
}
NativeModuleRef::NativeModuleRef(NativeModuleRef&& other) noexcept
: nativeModule{std::exchange(other.nativeModule, nullptr)}
{
}
NativeModuleRef& NativeModuleRef::operator=(NativeModuleRef other) noexcept
{
swap(other);
return *this;
}
NativeModuleRef::~NativeModuleRef() noexcept
{
reset();
}
void NativeModuleRef::reset() noexcept
{
if (nativeModule == nullptr)
return;
nativeModule->release();
nativeModule = nullptr;
}
void NativeModuleRef::swap(NativeModuleRef& other) noexcept
{
std::swap(nativeModule, other.nativeModule);
}
[[nodiscard]] bool NativeModuleRef::empty() const noexcept
{
return nativeModule == nullptr;
}
NativeModuleRef::operator bool() const noexcept
{
return nativeModule != nullptr;
}
[[nodiscard]] const NativeModule* NativeModuleRef::get() const noexcept
{
return nativeModule;
}
[[nodiscard]] const NativeModule* NativeModuleRef::operator->() const noexcept
{
return nativeModule;
}
[[nodiscard]] const NativeModule& NativeModuleRef::operator*() const noexcept
{
return *nativeModule;
}
SharedCodeAllocator::~SharedCodeAllocator() noexcept
{
// The allocator should not be destroyed until all outstanding references
// have been released and all allocated modules have been destroyed.
LUAU_ASSERT(nativeModules.empty());
}
[[nodiscard]] NativeModuleRef SharedCodeAllocator::tryGetNativeModule(const ModuleId& moduleId) const noexcept
{
std::unique_lock lock{mutex};
return tryGetNativeModuleWithLockHeld(moduleId);
}
NativeModuleRef SharedCodeAllocator::getOrInsertNativeModule(
const ModuleId& moduleId, std::vector<NativeProto> nativeProtos, const std::vector<uint8_t>& data, const std::vector<uint8_t>& code)
{
std::unique_lock lock{mutex};
if (NativeModuleRef existingModule = tryGetNativeModuleWithLockHeld(moduleId))
return existingModule;
// We simulate allocation until the backend allocator is integrated
std::unique_ptr<NativeModule>& nativeModule = nativeModules[moduleId];
nativeModule = std::make_unique<NativeModule>(this, moduleId, baseAddress, std::move(nativeProtos));
baseAddress += data.size() + code.size();
return NativeModuleRef{nativeModule.get()};
}
void SharedCodeAllocator::eraseNativeModuleIfUnreferenced(const ModuleId& moduleId)
{
std::unique_lock lock{mutex};
const auto it = nativeModules.find(moduleId);
if (it == nativeModules.end())
return;
// It is possible that someone acquired a reference to the module between
// the time that we called this function and the time that we acquired the
// lock. If so, that's okay.
if (it->second->getRefcount() != 0)
return;
nativeModules.erase(it);
}
[[nodiscard]] NativeModuleRef SharedCodeAllocator::tryGetNativeModuleWithLockHeld(const ModuleId& moduleId) const noexcept
{
const auto it = nativeModules.find(moduleId);
if (it == nativeModules.end())
return NativeModuleRef{};
return NativeModuleRef{it->second.get()};
}
[[nodiscard]] size_t SharedCodeAllocator::ModuleIdHash::operator()(const ModuleId& moduleId) const noexcept
{
return std::hash<std::string_view>{}(std::string_view{reinterpret_cast<const char*>(moduleId.data()), moduleId.size()});
}
} // namespace CodeGen
} // namespace Luau