// 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 "Luau/CodeAllocator.h" #include "Luau/CodeGenCommon.h" #include #include #include namespace Luau { namespace CodeGen { struct NativeProtoBytecodeIdEqual { [[nodiscard]] bool operator()(const NativeProtoExecDataPtr& left, const NativeProtoExecDataPtr& right) const noexcept { return getNativeProtoExecDataHeader(left.get()).bytecodeId == getNativeProtoExecDataHeader(right.get()).bytecodeId; } }; struct NativeProtoBytecodeIdLess { [[nodiscard]] bool operator()(const NativeProtoExecDataPtr& left, const NativeProtoExecDataPtr& right) const noexcept { return getNativeProtoExecDataHeader(left.get()).bytecodeId < getNativeProtoExecDataHeader(right.get()).bytecodeId; } [[nodiscard]] bool operator()(const NativeProtoExecDataPtr& left, uint32_t right) const noexcept { return getNativeProtoExecDataHeader(left.get()).bytecodeId < right; } [[nodiscard]] bool operator()(uint32_t left, const NativeProtoExecDataPtr& right) const noexcept { return left < getNativeProtoExecDataHeader(right.get()).bytecodeId; } }; NativeModule::NativeModule(SharedCodeAllocator* allocator, const std::optional& moduleId, const uint8_t* moduleBaseAddress, std::vector nativeProtos) noexcept : allocator{allocator} , moduleId{moduleId} , moduleBaseAddress{moduleBaseAddress} , nativeProtos{std::move(nativeProtos)} { CODEGEN_ASSERT(allocator != nullptr); CODEGEN_ASSERT(moduleBaseAddress != nullptr); // Bind all of the NativeProtos to this module: for (const NativeProtoExecDataPtr& nativeProto : this->nativeProtos) { NativeProtoExecDataHeader& header = getNativeProtoExecDataHeader(nativeProto.get()); header.nativeModule = this; header.entryOffsetOrAddress = moduleBaseAddress + reinterpret_cast(header.entryOffsetOrAddress); } std::sort(this->nativeProtos.begin(), this->nativeProtos.end(), NativeProtoBytecodeIdLess{}); // We should not have two NativeProtos for the same bytecode id: CODEGEN_ASSERT( std::adjacent_find(this->nativeProtos.begin(), this->nativeProtos.end(), NativeProtoBytecodeIdEqual{}) == this->nativeProtos.end()); } NativeModule::~NativeModule() noexcept { CODEGEN_ASSERT(refcount == 0); } size_t NativeModule::addRef() const noexcept { return refcount.fetch_add(1) + 1; } size_t NativeModule::addRefs(size_t count) const noexcept { return refcount.fetch_add(count) + count; } size_t NativeModule::release() const noexcept { size_t newRefcount = refcount.fetch_sub(1) - 1; if (newRefcount != 0) return newRefcount; allocator->eraseNativeModuleIfUnreferenced(*this); // 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 std::optional& NativeModule::getModuleId() const noexcept { return moduleId; } [[nodiscard]] const uint8_t* NativeModule::getModuleBaseAddress() const noexcept { return moduleBaseAddress; } [[nodiscard]] const uint32_t* 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; CODEGEN_ASSERT(std::next(range.first) == range.second); return range.first->get(); } [[nodiscard]] const std::vector& NativeModule::getNativeProtos() const noexcept { return nativeProtos; } NativeModuleRef::NativeModuleRef(const 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(CodeAllocator* codeAllocator) noexcept : codeAllocator{codeAllocator} { } SharedCodeAllocator::~SharedCodeAllocator() noexcept { // The allocator should not be destroyed until all outstanding references // have been released and all allocated modules have been destroyed. CODEGEN_ASSERT(identifiedModules.empty()); CODEGEN_ASSERT(anonymousModuleCount == 0); } [[nodiscard]] NativeModuleRef SharedCodeAllocator::tryGetNativeModule(const ModuleId& moduleId) const noexcept { std::unique_lock lock{mutex}; return tryGetNativeModuleWithLockHeld(moduleId); } std::pair SharedCodeAllocator::getOrInsertNativeModule(const ModuleId& moduleId, std::vector nativeProtos, const uint8_t* data, size_t dataSize, const uint8_t* code, size_t codeSize) { std::unique_lock lock{mutex}; if (NativeModuleRef existingModule = tryGetNativeModuleWithLockHeld(moduleId)) return {std::move(existingModule), false}; uint8_t* nativeData = nullptr; size_t sizeNativeData = 0; uint8_t* codeStart = nullptr; if (!codeAllocator->allocate(data, int(dataSize), code, int(codeSize), nativeData, sizeNativeData, codeStart)) { return {}; } std::unique_ptr& nativeModule = identifiedModules[moduleId]; nativeModule = std::make_unique(this, moduleId, codeStart, std::move(nativeProtos)); return {NativeModuleRef{nativeModule.get()}, true}; } NativeModuleRef SharedCodeAllocator::insertAnonymousNativeModule( std::vector nativeProtos, const uint8_t* data, size_t dataSize, const uint8_t* code, size_t codeSize) { std::unique_lock lock{mutex}; uint8_t* nativeData = nullptr; size_t sizeNativeData = 0; uint8_t* codeStart = nullptr; if (!codeAllocator->allocate(data, int(dataSize), code, int(codeSize), nativeData, sizeNativeData, codeStart)) { return {}; } NativeModuleRef nativeModuleRef{new NativeModule{this, std::nullopt, codeStart, std::move(nativeProtos)}}; ++anonymousModuleCount; return nativeModuleRef; } void SharedCodeAllocator::eraseNativeModuleIfUnreferenced(const NativeModule& nativeModule) { std::unique_lock lock{mutex}; // 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 (nativeModule.getRefcount() != 0) return; if (const std::optional& moduleId = nativeModule.getModuleId()) { const auto it = identifiedModules.find(*moduleId); CODEGEN_ASSERT(it != identifiedModules.end()); identifiedModules.erase(it); } else { CODEGEN_ASSERT(anonymousModuleCount.fetch_sub(1) != 0); delete &nativeModule; } } [[nodiscard]] NativeModuleRef SharedCodeAllocator::tryGetNativeModuleWithLockHeld(const ModuleId& moduleId) const noexcept { const auto it = identifiedModules.find(moduleId); if (it == identifiedModules.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{reinterpret_cast(moduleId.data()), moduleId.size()}); } } // namespace CodeGen } // namespace Luau