//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// #include "swift/IDE/CodeCompletionCache.h" #include "swift/Basic/Assertions.h" #include "swift/Basic/Cache.h" #include "swift/Basic/StringExtras.h" #include "llvm/ADT/APInt.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/StringMap.h" #include "llvm/Support/EndianStream.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" using namespace swift; using namespace ide; namespace swift { namespace ide { struct CodeCompletionCacheImpl { using Key = CodeCompletionCache::Key; using Value = CodeCompletionCache::Value; using ValueRefCntPtr = CodeCompletionCache::ValueRefCntPtr; sys::Cache TheCache{"swift.libIDE.CodeCompletionCache"}; }; } // end namespace ide } // end namespace swift namespace swift { namespace sys { template<> struct CacheValueCostInfo { static size_t getCost(const swift::ide::CodeCompletionCacheImpl::Value &V) { return V.Allocator->getTotalMemory(); } }; } // namespace sys } // namespace swift CodeCompletionCache::ValueRefCntPtr CodeCompletionCache::createValue() { return ValueRefCntPtr(new Value); } std::optional CodeCompletionCache::get(const Key &K) { auto &TheCache = Impl->TheCache; std::optional V = TheCache.get(K); if (V) { // Check whether V is up to date. llvm::sys::fs::file_status ModuleStatus; if (llvm::sys::fs::status(K.ModuleFilename, ModuleStatus) || V.value()->ModuleModificationTime != ModuleStatus.getLastModificationTime()) { // Cache is stale. V = std::nullopt; TheCache.remove(K); } } else if (nextCache && (V = nextCache->get(K))) { // Hit the chained cache. Update our own cache to match. setImpl(K, *V, /*setChain*/ false); } return V; } void CodeCompletionCache::setImpl(const Key &K, ValueRefCntPtr V, bool setChain) { { assert(!K.ModuleFilename.empty()); llvm::sys::fs::file_status ModuleStatus; if (llvm::sys::fs::status(K.ModuleFilename, ModuleStatus)) { V->ModuleModificationTime = std::chrono::system_clock::now(); return; } else { V->ModuleModificationTime = ModuleStatus.getLastModificationTime(); } } Impl->TheCache.set(K, V); // FIXME: we could write the results to disk in the background, since they're // immutable at this point. if (nextCache && setChain) nextCache->set(K, V); } CodeCompletionCache::CodeCompletionCache(OnDiskCodeCompletionCache *nextCache) : Impl(new CodeCompletionCacheImpl()), nextCache(nextCache) {} CodeCompletionCache::~CodeCompletionCache() {} /// A version number for the format of the serialized code completion results. /// /// This should be incremented any time we commit a change to the format of the /// cached results. This isn't expected to change very often. static constexpr uint32_t onDiskCompletionCacheVersion = 12; // Removed 'IsAsync'. /// Deserializes CodeCompletionResults from \p in and stores them in \p V. /// \see writeCacheModule. static bool readCachedModule(llvm::MemoryBuffer *in, const CodeCompletionCache::Key &K, CodeCompletionCache::Value &V, bool allowOutOfDate = false) { const char *cursor = in->getBufferStart(); const char *end = in->getBufferEnd(); auto read32le = [end](const char *&cursor) { auto result = llvm::support::endian::read32le(cursor); cursor += sizeof(result); assert(cursor <= end); (void)end; return result; }; // HEADER { auto version = read32le(cursor); if (version != onDiskCompletionCacheVersion) return false; // File written with different format. auto mtime = llvm::support::endian::read64le(cursor); cursor += sizeof(mtime); // Check the module file's last modification time. if (!allowOutOfDate) { llvm::sys::fs::file_status status; if (llvm::sys::fs::status(K.ModuleFilename, status) || status.getLastModificationTime().time_since_epoch().count() != std::chrono::nanoseconds(mtime).count()) { return false; // Out of date, or doesn't exist. } } } // DEBUG INFO cursor += read32le(cursor); // Skip the whole debug section. // Get the size of the various sections. auto resultSize = read32le(cursor); const char *resultEnd = cursor + resultSize; const char *chunks = resultEnd; auto chunkSize = read32le(chunks); const char *strings = chunks + chunkSize; auto stringsSize = read32le(strings); const char *types = strings + stringsSize; auto typesSize = read32le(types); assert(types + typesSize == end && "incorrect file size"); (void)typesSize; // so it is not seen as "unused" in release builds. // STRINGS llvm::DenseMap knownStrings; auto getString = [&](uint32_t index) -> NullTerminatedStringRef { if (index == ~0u) return ""; auto found = knownStrings.find(index); if (found != knownStrings.end()) { return found->second; } const char *p = strings + index; size_t size = read32le(p); auto str = NullTerminatedStringRef(StringRef(p, size), *V.Allocator); knownStrings[index] = str; return str; }; // TYPES llvm::DenseMap knownTypes; std::function getType = [&](uint32_t index) -> const USRBasedType * { auto found = knownTypes.find(index); if (found != knownTypes.end()) { return found->second; } const char *p = types + index; auto usrLength = read32le(p); auto usr = StringRef(p, usrLength); p += usrLength; auto supertypesCount = read32le(p); std::vector supertypes; supertypes.reserve(supertypesCount); for (unsigned i = 0; i < supertypesCount; i++) { auto supertypeIndex = read32le(p); supertypes.push_back(getType(supertypeIndex)); } auto customAttributeKinds = OptionSet(*p++); const USRBasedType *res = USRBasedType::fromUSR( usr, supertypes, customAttributeKinds, V.USRTypeArena); knownTypes[index] = res; return res; }; // CHUNKS auto getCompletionString = [&](uint32_t chunkIndex) { const char *p = chunks + chunkIndex; auto len = read32le(p); using Chunk = CodeCompletionString::Chunk; SmallVector chunkList; for (unsigned j = 0; j < len; ++j) { auto kind = static_cast(*p++); auto nest = *p++; auto isAnnotation = static_cast(*p++); auto textIndex = read32le(p); auto text = getString(textIndex); if (Chunk::chunkHasText(kind)) { chunkList.push_back( Chunk::createWithText(kind, nest, text, isAnnotation)); } else { chunkList.push_back(Chunk::createSimple(kind, nest, isAnnotation)); } } return CodeCompletionString::create(*V.Allocator, chunkList); }; // RESULTS while (cursor != resultEnd) { auto kind = static_cast(*cursor++); auto associatedKind = static_cast(*cursor++); auto opKind = static_cast(*cursor++); auto roles = CodeCompletionMacroRoles(*cursor++); auto notRecommended = static_cast(*cursor++); auto diagSeverity = static_cast(*cursor++); auto isSystem = static_cast(*cursor++); auto hasAsyncAlternative = static_cast(*cursor++); auto chunkIndex = read32le(cursor); auto moduleIndex = read32le(cursor); auto briefDocIndex = read32le(cursor); auto diagMessageIndex = read32le(cursor); auto filterNameIndex = read32le(cursor); auto nameForDiagnosticsIndex = read32le(cursor); auto swiftUSRIndex = read32le(cursor); auto assocUSRCount = read32le(cursor); SmallVector assocUSRs; for (unsigned i = 0; i < assocUSRCount; ++i) { assocUSRs.push_back(getString(read32le(cursor))); } auto resultTypesCount = read32le(cursor); SmallVector resultTypes; resultTypes.reserve(resultTypesCount); for (size_t i = 0; i < resultTypesCount; i++) { resultTypes.push_back(getType(read32le(cursor))); } CodeCompletionString *string = getCompletionString(chunkIndex); auto moduleName = getString(moduleIndex); auto briefDocComment = getString(briefDocIndex); auto diagMessage = getString(diagMessageIndex); auto filterName = getString(filterNameIndex); auto nameForDiagnostics = getString(nameForDiagnosticsIndex); auto swiftUSR = getString(swiftUSRIndex); ContextFreeCodeCompletionResult *result = new (*V.Allocator) ContextFreeCodeCompletionResult( kind, associatedKind, opKind, roles, isSystem, hasAsyncAlternative, string, moduleName, briefDocComment, llvm::ArrayRef(assocUSRs).copy(*V.Allocator), swiftUSR, CodeCompletionResultType(resultTypes), notRecommended, diagSeverity, diagMessage, filterName, nameForDiagnostics); V.Results.push_back(result); } return true; } /// Writes the code completion results from the sink for \p V to \p out. /// /// The high-level format is: /// /// HEADER /// * version, which **must be bumped** if we change the format! /// * mtime for the module file /// /// KEY /// * the original CodeCompletionCache::Key, used for debugging the cache. /// /// RESULTS /// * A length-prefixed array of fixed size CodeCompletionResults. /// * Contains offsets into CHUNKS and STRINGS. /// /// CHUNKS /// * A length-prefixed array of CodeCompletionStrings. /// * Each CodeCompletionString is a length-prefixed array of fixed size /// CodeCompletionString::Chunks. /// /// STRINGS /// * A blob of length-prefixed strings referred to in CHUNKS or RESULTS. static void writeCachedModule(llvm::raw_ostream &out, const CodeCompletionCache::Key &K, CodeCompletionCache::Value &V) { llvm::support::endian::Writer LE(out, llvm::endianness::little); // HEADER // Metadata required for reading the completions. LE.write(onDiskCompletionCacheVersion); // Version auto mtime = V.ModuleModificationTime.time_since_epoch().count(); LE.write(mtime); // Mtime for module file // KEY // We don't need the stored key to load the results, but it is useful if we // want to debug the cache itself. { SmallString<256> scratch; llvm::raw_svector_ostream OSS(scratch); OSS << K.ModuleFilename << "\0"; OSS << K.ModuleName << "\0"; llvm::support::endian::Writer OSSLE(OSS, llvm::endianness::little); OSSLE.write(K.AccessPath.size()); for (StringRef p : K.AccessPath) OSS << p << "\0"; OSSLE.write(K.ResultsHaveLeadingDot); OSSLE.write(K.ForTestableLookup); OSSLE.write(K.ForPrivateImportLookup); OSSLE.write(K.AddInitsInToplevel); OSSLE.write(K.AddCallWithNoDefaultArgs); OSSLE.write(K.Annotated); LE.write(static_cast(OSS.tell())); // Size of debug info out.write(OSS.str().data(), OSS.str().size()); // Debug info blob } // String streams for writing to the CHUNKS and STRINGS sections. std::string results_; llvm::raw_string_ostream results(results_); std::string chunks_; llvm::raw_string_ostream chunks(chunks_); llvm::support::endian::Writer chunksLE(chunks, llvm::endianness::little); std::string strings_; llvm::raw_string_ostream strings(strings_); llvm::StringMap knownStrings; std::string types_; llvm::raw_string_ostream types(types_); llvm::DenseMap knownTypes; auto addString = [&strings, &knownStrings](StringRef str) { if (str.empty()) return ~0u; auto found = knownStrings.find(str); if (found != knownStrings.end()) { return found->second; } auto size = strings.tell(); llvm::support::endian::Writer LE(strings, llvm::endianness::little); LE.write(static_cast(str.size())); strings << str; knownStrings[str] = size; return static_cast(size); }; std::function addType = [&types, &knownTypes, &addType](const USRBasedType *type) -> uint32_t { auto found = knownTypes.find(type); if (found != knownTypes.end()) { return found->second; } std::vector supertypeIndicies; // IMPORTANT: To compute the supertype indicies, we might need to add // entries to the type table by calling addType recursively. Thus, we must // perform this calculation before writing any bytes of this type to the // types table. auto supertypes = type->getSupertypes(); supertypeIndicies.reserve(supertypes.size()); for (auto supertype : supertypes) { supertypeIndicies.push_back(addType(supertype)); } auto size = types.tell(); llvm::support::endian::Writer LE(types, llvm::endianness::little); StringRef USR = type->getUSR(); LE.write(static_cast(USR.size())); types << USR; LE.write(static_cast(supertypeIndicies.size())); for (auto supertypeIndex : supertypeIndicies) { LE.write(static_cast(supertypeIndex)); } OptionSet customAttributeKinds = type->getCustomAttributeKinds(); LE.write(static_cast(customAttributeKinds.toRaw())); knownTypes[type] = size; return static_cast(size); }; auto addCompletionString = [&](const CodeCompletionString *str) { auto size = chunks.tell(); chunksLE.write(static_cast(str->getChunks().size())); for (auto chunk : str->getChunks()) { chunksLE.write(static_cast(chunk.getKind())); chunksLE.write(static_cast(chunk.getNestingLevel())); chunksLE.write(static_cast(chunk.isAnnotation())); if (chunk.hasText()) { chunksLE.write(addString(chunk.getText())); } else { chunksLE.write(static_cast(~0u)); } } return static_cast(size); }; // RESULTS { llvm::support::endian::Writer LE(results, llvm::endianness::little); for (const ContextFreeCodeCompletionResult *R : V.Results) { // FIXME: compress bitfield LE.write(static_cast(R->getKind())); LE.write(static_cast(R->getOpaqueAssociatedKind())); if (R->isOperator()) { LE.write(static_cast(R->getKnownOperatorKind())); } else { LE.write(static_cast(CodeCompletionOperatorKind::None)); } LE.write(static_cast(R->getMacroRoles().toRaw())); LE.write(static_cast(R->getNotRecommendedReason())); LE.write(static_cast(R->getDiagnosticSeverity())); LE.write(static_cast(R->isSystem())); LE.write(static_cast(R->hasAsyncAlternative())); LE.write( static_cast(addCompletionString(R->getCompletionString()))); LE.write(addString(R->getModuleName())); // index into strings LE.write(addString(R->getBriefDocComment())); // index into strings LE.write(addString(R->getDiagnosticMessage())); // index into strings LE.write(addString(R->getFilterName())); // index into strings LE.write(addString(R->getNameForDiagnostics())); // index into strings LE.write(addString(R->getSwiftUSR())); // index into strings LE.write(static_cast(R->getAssociatedUSRs().size())); for (unsigned i = 0; i < R->getAssociatedUSRs().size(); ++i) { LE.write(addString(R->getAssociatedUSRs()[i])); } auto resultTypes = R->getResultType().getUSRBasedResultTypes(V.USRTypeArena); LE.write(static_cast(resultTypes.size())); for (auto resultType : resultTypes) { LE.write(addType(resultType)); // index into types } } } LE.write(static_cast(results.tell())); out << results.str(); // CHUNKS LE.write(static_cast(chunks.tell())); out << chunks.str(); // STRINGS LE.write(static_cast(strings.tell())); out << strings.str(); // TYPES LE.write(static_cast(types.tell())); out << types.str(); } /// Get the name for the cached code completion results for a given key \p K in /// \p cacheDirectory. /// /// This name is unique (modulo hash collisions) to the key \p K. static std::string getName(StringRef cacheDirectory, const CodeCompletionCache::Key &K) { SmallString<128> name(cacheDirectory); // cacheDirectory/ModuleName llvm::sys::path::append(name, K.ModuleName); llvm::raw_svector_ostream OSS(name); // name[-with-enabled-options] OSS << (K.ResultsHaveLeadingDot ? "-dot" : "") << (K.ForTestableLookup ? "-testable" : "") << (K.ForPrivateImportLookup ? "-private" : "") << (K.AddInitsInToplevel ? "-inits" : "") << (K.AddCallWithNoDefaultArgs ? "-nodefaults" : "") << (K.Annotated ? "-annotated" : ""); if (K.SpiGroups.size() > 0) { OSS << "-spi"; for (auto SpiGroup : K.SpiGroups) { OSS << "-" << SpiGroup; } } // name[-access-path-components] for (StringRef component : K.AccessPath) OSS << "-" << component; // name- auto hash = llvm::hash_value(K.ModuleFilename); SmallString<16> hashStr; llvm::APInt(64, uint64_t(hash)).toStringUnsigned(hashStr, /*Radix*/ 36); OSS << "-" << hashStr << ".completions"; return std::string(name.str()); } std::optional OnDiskCodeCompletionCache::get(const Key &K) { // Try to find the cached file. auto bufferOrErr = llvm::MemoryBuffer::getFile(getName(cacheDirectory, K)); if (!bufferOrErr) return std::nullopt; // Read the cached results, failing if they are out of date. auto V = CodeCompletionCache::createValue(); if (!readCachedModule(bufferOrErr.get().get(), K, *V)) return std::nullopt; return V; } std::error_code OnDiskCodeCompletionCache::set(const Key &K, ValueRefCntPtr V) { if (K.ModuleFilename.empty()) return std::make_error_code(std::errc::no_such_file_or_directory); // Create the cache directory if it doesn't exist. if (auto err = llvm::sys::fs::create_directories(cacheDirectory)) return err; std::string name = getName(cacheDirectory, K); // Create a temporary file to write the results into. SmallString<128> tmpName(name + "-%%%%%%"); int tmpFD; if (auto err = llvm::sys::fs::createUniqueFile(tmpName.str(), tmpFD, tmpName)) return err; // Write the contents of the buffer. llvm::raw_fd_ostream out(tmpFD, /*shouldClose=*/true); writeCachedModule(out, K, *V); out.flush(); if (out.has_error()) return std::make_error_code(std::errc::io_error); // Atomically rename the file into its final location. return llvm::sys::fs::rename(tmpName.str(), name); } std::optional OnDiskCodeCompletionCache::getFromFile(StringRef filename) { // Try to find the cached file. auto bufferOrErr = llvm::MemoryBuffer::getFile(filename); if (!bufferOrErr) return std::nullopt; // Make up a key for readCachedModule. CodeCompletionCache::Key K{/*ModuleFilename=*/filename.str(), /*ModuleName=*/"", /*AccessPath=*/{}, /*ResultsHaveLeadingDot=*/false, /*ForTestableLookup=*/false, /*ForPrivateImportLookup=*/false, /*SpiGroups=*/{}, /*AddInitsInToplevel=*/false, /*AddCallWithNoDefaultArgs=*/false, /*Annotated=*/false}; // Read the cached results. auto V = CodeCompletionCache::createValue(); if (!readCachedModule(bufferOrErr.get().get(), K, *V, /*allowOutOfDate*/ true)) return std::nullopt; return V; } OnDiskCodeCompletionCache::OnDiskCodeCompletionCache(Twine cacheDirectory) : cacheDirectory(cacheDirectory.str()) {} OnDiskCodeCompletionCache::~OnDiskCodeCompletionCache() {}