diff options
| -rw-r--r-- | AGENTS/SUBSYSTEMS/STDLIB.md | 6 | ||||
| -rw-r--r-- | doc/cpsc.1.md | 5 | ||||
| -rw-r--r-- | tools/driver/NativeCompiler.cc | 3 | ||||
| -rw-r--r-- | tools/driver/StandardLibrary.cc | 92 | ||||
| -rw-r--r-- | tools/driver/StandardLibrary.h | 4 |
5 files changed, 88 insertions, 22 deletions
diff --git a/AGENTS/SUBSYSTEMS/STDLIB.md b/AGENTS/SUBSYSTEMS/STDLIB.md index a4e260a..196ed28 100644 --- a/AGENTS/SUBSYSTEMS/STDLIB.md +++ b/AGENTS/SUBSYSTEMS/STDLIB.md @@ -10,7 +10,11 @@ import rules: [CONVENTIONS/EXCEPTIONS_AND_UNITS.md](../CONVENTIONS/EXCEPTIONS_AN - The default build compiles each .cc with the host compiler into build/pseudocode-lib/*.o, copies .cps units beside them, and embeds all these outputs in cpsc. -- StandardLibrary extracts the resources through linking/execution; the driver +- StandardLibrary extracts the resources through linking/execution into a per-user, + content-keyed cache (`llvm::sys::path::cache_directory()/cpsc/std-<xxh3 hex>`). Files are + reused when identical, otherwise published by atomic rename; the cache is never deleted, so + a killed run leaks nothing. Only if no cache directory is usable does it fall back to a + temporary `cpsc-std` directory removed on exit. The driver implicitly imports std.cps unless -no-std is set. std.cps imports string.cps. - Source/object resources are embedded in installed/released cpsc, which needs no separate library directory; manually linking saved IR still requires the matching diff --git a/doc/cpsc.1.md b/doc/cpsc.1.md index 6a81a8a..c0dd4f0 100644 --- a/doc/cpsc.1.md +++ b/doc/cpsc.1.md @@ -126,6 +126,11 @@ Missing input, including invoking `cpsc` without arguments, reports `cpsc: fatal Generated IR uses the C++ exception ABI. Native binaries need the C++ runtime; EMBEDDED includes it, while PATH uses the runtime available to `lli`. Nightly CI runs embedded exception tests on Linux, Windows and macOS with LLVM 23. +# FILES + +*cache directory*/cpsc/std-*hash* +: Per-user cache of the embedded standard library sources and objects, where *cache directory* is `$XDG_CACHE_HOME` or `~/.cache` on Linux, `~/Library/Caches` on macOS and the local application data directory on Windows. Directories are named by a hash of their content, files are replaced atomically, and `cpsc` never deletes them; they may be removed at any time. If no cache directory can be created, `cpsc` extracts to a temporary directory and removes it on exit. + # SEE ALSO [cpsc-language-overview(7)](language/cpsc-language-overview.7.md) describes supported syntax and links to the keyword, builtin and standard-library function manuals. diff --git a/tools/driver/NativeCompiler.cc b/tools/driver/NativeCompiler.cc index dc1f068..fa3d2a1 100644 --- a/tools/driver/NativeCompiler.cc +++ b/tools/driver/NativeCompiler.cc @@ -337,8 +337,7 @@ bool compileNative(const cps::CodeGen &CG, llvm::StringRef OutputPath, const cha #endif LinkArgs.append(Job.getArguments().begin(), Job.getArguments().end()); auto Result = lld::lldMain(LinkArgs, llvm::outs(), llvm::errs(), Linkers); - if (!Result.canRunAgain) std::exit(1); - if (Result.retCode != 0) return fail("native linking failed"); + if (!Result.canRunAgain || Result.retCode != 0) return fail("native linking failed"); if (auto EC = llvm::sys::fs::setPermissions(StagedPath, llvm::sys::fs::all_read | llvm::sys::fs::owner_write | llvm::sys::fs::all_exe)) return fail("cannot set executable permissions: " + EC.message()); diff --git a/tools/driver/StandardLibrary.cc b/tools/driver/StandardLibrary.cc index 7680054..a2b3168 100644 --- a/tools/driver/StandardLibrary.cc +++ b/tools/driver/StandardLibrary.cc @@ -1,8 +1,12 @@ #include "StandardLibrary.h" #include "cps/Diagnostics.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Support/xxhash.h" namespace { struct LibraryFile { @@ -14,31 +18,83 @@ struct LibraryFile { } StandardLibrary::~StandardLibrary() { - if (!Directory.empty()) llvm::sys::fs::remove_directories(Directory); + if (!Directory.empty() && Temporary) llvm::sys::fs::remove_directories(Directory); +} + +namespace { +std::string contentKey() { + std::string Bytes; + for (const auto &File : LibraryFiles) { + Bytes.append(File.Name); + Bytes.push_back('\0'); + Bytes.append(File.Data, File.Size); + } + return llvm::utohexstr(llvm::xxh3_64bits(llvm::ArrayRef<uint8_t>( + reinterpret_cast<const uint8_t *>(Bytes.data()), Bytes.size())), true); +} + +bool sameContents(const llvm::Twine &Path, const LibraryFile &File) { + auto Existing = llvm::MemoryBuffer::getFile(Path); + return Existing && (*Existing)->getBuffer() == llvm::StringRef(File.Data, File.Size); +} + +// Atomically publishes the bytes at Path; concurrent runs never see partial files. +std::error_code publish(llvm::StringRef Directory, llvm::StringRef Path, const LibraryFile &File) { + llvm::SmallString<256> Pattern(Directory); + llvm::sys::path::append(Pattern, "tmp-%%%%%%%%"); + auto Temp = llvm::sys::fs::TempFile::create(Pattern); + if (!Temp) return llvm::errorToErrorCode(Temp.takeError()); + llvm::raw_fd_ostream Output(Temp->FD, false); + Output.write(File.Data, File.Size); + Output.flush(); + if (Output.has_error()) { + auto EC = Output.error(); + Output.clear_error(); + llvm::consumeError(Temp->discard()); + return EC; + } + if (auto Error = Temp->keep(Path)) return llvm::errorToErrorCode(std::move(Error)); + return {}; +} } bool StandardLibrary::load(std::vector<std::string> &Inputs) { - auto Error = llvm::sys::fs::createUniqueDirectory("cpsc-std", Directory); - if (Error) { - llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) - << "cannot extract standard library: " << Error.message() << "\n"; - return false; + // The extracted files are cached per user and keyed by content, so killed runs leave nothing behind. + llvm::SmallString<256> Cache; + if (llvm::sys::path::cache_directory(Cache)) { + llvm::sys::path::append(Cache, "cpsc", "std-" + contentKey()); + if (!llvm::sys::fs::create_directories(Cache)) Directory = Cache; + } + if (Directory.empty()) { + Temporary = true; + if (auto Error = llvm::sys::fs::createUniqueDirectory("cpsc-std", Directory)) { + llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) + << "cannot extract standard library: " << Error.message() << "\n"; + return false; + } } for (const auto &File : LibraryFiles) { llvm::SmallString<256> Path(Directory); llvm::sys::path::append(Path, File.Name); - llvm::raw_fd_ostream Output(Path, Error); - if (!Error) { - Output.write(File.Data, File.Size); - Output.close(); - Error = Output.error(); - } - if (Error) { - llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) - << "cannot extract standard library file '" << File.Name - << "': " << Error.message() << "\n"; - Output.clear_error(); - return false; + if (!(!Temporary && sameContents(Path, File))) { + std::error_code Error; + if (Temporary) { + llvm::raw_fd_ostream Output(Path, Error); + if (!Error) { + Output.write(File.Data, File.Size); + Output.close(); + Error = Output.error(); + Output.clear_error(); + } + } else { + Error = publish(Directory, Path, File); + } + if (Error) { + llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) + << "cannot extract standard library file '" << File.Name + << "': " << Error.message() << "\n"; + return false; + } } if (llvm::sys::path::extension(Path) == ".cps") Inputs.emplace_back(Path); } diff --git a/tools/driver/StandardLibrary.h b/tools/driver/StandardLibrary.h index 5aa4f8c..0e641d0 100644 --- a/tools/driver/StandardLibrary.h +++ b/tools/driver/StandardLibrary.h @@ -3,9 +3,11 @@ #include <string> #include <vector> -// Extracted files live through native linking and runner execution. +// Extracted files live through native linking and runner execution. They are cached per user +// (never deleted); only the fallback temporary directory is removed on destruction. class StandardLibrary { llvm::SmallString<256> Directory; + bool Temporary = false; public: ~StandardLibrary(); bool load(std::vector<std::string> &Inputs); |
