aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AGENTS/SUBSYSTEMS/STDLIB.md6
-rw-r--r--doc/cpsc.1.md5
-rw-r--r--tools/driver/NativeCompiler.cc3
-rw-r--r--tools/driver/StandardLibrary.cc92
-rw-r--r--tools/driver/StandardLibrary.h4
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);