aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-18 08:11:39 +0000
committer_ov4 <ov4@ov4.org>2026-10-18 08:11:39 +0000
commitcd3c09a5c2566d674680193042baf6474b6d0976 (patch)
tree50276c3de1ee4262eccbddb4240bf4267c04a15f
parent30fced09675acfd2b9ab6db6be8359a70d3f0402 (diff)
feat(driver): load the embedded C++ runtime into the JIT
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--AGENTS/ARCH.md2
-rw-r--r--AGENTS/TESTING.md2
-rw-r--r--tests/i/extern-cc-runtime/case.json1
-rw-r--r--tests/i/extern-cc-runtime/expected.txt2
-rw-r--r--tests/i/extern-cc-runtime/program.cps9
-rw-r--r--tests/i/extern-cc-runtime/runtime.cc13
-rw-r--r--tools/driver/EmbeddedRunner.cc54
-rw-r--r--tools/driver/NativeCompiler.cc20
-rw-r--r--tools/driver/NativeCompiler.h5
9 files changed, 107 insertions, 1 deletions
diff --git a/AGENTS/ARCH.md b/AGENTS/ARCH.md
index 2e2ec2d..eb55466 100644
--- a/AGENTS/ARCH.md
+++ b/AGENTS/ARCH.md
@@ -355,3 +355,5 @@ See [MAN.md](MAN.md) before editing the command or syntax manuals. It defines
source layout, naming, prose formatting and the small checks needed for changes.
**Library units.** Line-leading `#LIB` marks SourceUnit::IsLibrary. RuntimeCheck uses the externally visible i32 `__cps_user_line`; FunctionGen’s shared call hook updates it only on non-library to library calls. Runtime helper line arguments use RuntimeCheck::lineValue too. Compiler diagnostics retain the owning file location.
+
+The embedded JIT lazily loads the captured C++ standard-library and compiler-support archives using StaticLibraryDefinitionGenerator. Members defining exception ABI or unwind symbols are filtered so all exception frames use the host runtime; additional host ABI functions are exposed explicitly. Archive buffers own their extracted bytes for the JIT lifetime.
diff --git a/AGENTS/TESTING.md b/AGENTS/TESTING.md
index ac8c458..2d7685a 100644
--- a/AGENTS/TESTING.md
+++ b/AGENTS/TESTING.md
@@ -12,7 +12,7 @@ the reason, line and exact absolute input path must match the same diagnostic.
The harness contract tests reject wrong filenames, filename prefixes and split
reason/location messages.
-Suite i and ii cases may list `objects` as `{source, output}` pairs, for example `lib.c` to `lib.o`. The runner copies the case directory beside the results, builds each object there with the host C (`.c`) or C++ (`.cc`) compiler passed by `--cc` and `--cxx`, and compiles `sources` from the copy so `INCLUDE_OBJ "lib.o"` resolves. Keep C++ objects free of the C++ runtime library: `-r` loads only the C library.
+Suite i and ii cases may list `objects` as `{source, output}` pairs, for example `lib.c` to `lib.o`. The runner copies the case directory beside the results, builds each object there with the host C (`.c`) or C++ (`.cc`) compiler passed by `--cc` and `--cxx`, and compiles `sources` from the copy so `INCLUDE_OBJ "lib.o"` resolves. C++ objects may use the embedded C++ runtime in EMBEDDED mode; the JIT lazily loads the captured standard-library and compiler-support archives while retaining the host exception ABI. PATH mode depends on the external lli runtime configuration.
Optional manifest fields are `exit_code` for expected program status (default 0) and `timeout` for execution/reference seconds (default 30; file-intensive cases use 180). Compilation has a 120-second timeout. Do not make a known compiler defect pass by changing the expectation, adding an exemption or comparing only a permissive substring of program output.
diff --git a/tests/i/extern-cc-runtime/case.json b/tests/i/extern-cc-runtime/case.json
new file mode 100644
index 0000000..604feef
--- /dev/null
+++ b/tests/i/extern-cc-runtime/case.json
@@ -0,0 +1 @@
+{"objects":[{"source":"runtime.cc","output":"runtime.o"}]}
diff --git a/tests/i/extern-cc-runtime/expected.txt b/tests/i/extern-cc-runtime/expected.txt
new file mode 100644
index 0000000..3e5a8c6
--- /dev/null
+++ b/tests/i/extern-cc-runtime/expected.txt
@@ -0,0 +1,2 @@
+4099
+caught C++
diff --git a/tests/i/extern-cc-runtime/program.cps b/tests/i/extern-cc-runtime/program.cps
new file mode 100644
index 0000000..74d0386
--- /dev/null
+++ b/tests/i/extern-cc-runtime/program.cps
@@ -0,0 +1,9 @@
+INCLUDE_OBJ "runtime.o"
+EXTERN "C" EXTERN_FUNCTION string_length() RETURNS C_INT
+EXTERN "C" EXTERN_PROCEDURE string_throw()
+OUTPUT (INTEGER)string_length()
+TRY
+ CALL string_throw()
+CATCH
+ OUTPUT "caught C++"
+ENDTRY
diff --git a/tests/i/extern-cc-runtime/runtime.cc b/tests/i/extern-cc-runtime/runtime.cc
new file mode 100644
index 0000000..fd57538
--- /dev/null
+++ b/tests/i/extern-cc-runtime/runtime.cc
@@ -0,0 +1,13 @@
+#include <string>
+#include <stdexcept>
+extern "C" int string_length() {
+ std::string text(4096, 'x');
+ text += std::to_string(123);
+ return static_cast<int>(text.size());
+}
+extern "C" void string_throw() {
+ try { throw std::invalid_argument("string failure"); }
+ catch (const std::exception &e) {
+ if (std::string(e.what()) == "string failure") throw;
+ }
+}
diff --git a/tools/driver/EmbeddedRunner.cc b/tools/driver/EmbeddedRunner.cc
index f7077f2..3eedf8f 100644
--- a/tools/driver/EmbeddedRunner.cc
+++ b/tools/driver/EmbeddedRunner.cc
@@ -1,4 +1,6 @@
#include "EmbeddedRunner.h"
+#include "NativeCompiler.h"
+#include "llvm/Object/ObjectFile.h"
#include "cps/CodeGen.h"
#include "llvm/ExecutionEngine/Orc/AbsoluteSymbols.h"
#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
@@ -321,6 +323,22 @@ static llvm::Error defineRuntimeSymbols(llvm::orc::LLJIT &J) {
using llvm::orc::ExecutorSymbolDef;
Symbols[Intern("__cxa_allocate_exception")] =
ExecutorSymbolDef::fromPtr(&__cxxabiv1::__cxa_allocate_exception);
+ Symbols[Intern("__cxa_current_exception_type")] =
+ ExecutorSymbolDef::fromPtr(&__cxxabiv1::__cxa_current_exception_type);
+ Symbols[Intern("__cxa_demangle")] =
+ ExecutorSymbolDef::fromPtr(&__cxxabiv1::__cxa_demangle);
+ Symbols[Intern("__cxa_free_exception")] =
+ ExecutorSymbolDef::fromPtr(&__cxxabiv1::__cxa_free_exception);
+ Symbols[Intern("__cxa_rethrow")] =
+ ExecutorSymbolDef::fromPtr(&__cxxabiv1::__cxa_rethrow);
+ Symbols[Intern("__cxa_get_exception_ptr")] =
+ ExecutorSymbolDef::fromPtr(&__cxxabiv1::__cxa_get_exception_ptr);
+ Symbols[Intern("__cxa_guard_acquire")] =
+ ExecutorSymbolDef::fromPtr(&__cxxabiv1::__cxa_guard_acquire);
+ Symbols[Intern("__cxa_guard_release")] =
+ ExecutorSymbolDef::fromPtr(&__cxxabiv1::__cxa_guard_release);
+ Symbols[Intern("__cxa_guard_abort")] =
+ ExecutorSymbolDef::fromPtr(&__cxxabiv1::__cxa_guard_abort);
Symbols[Intern("__cxa_throw")] =
ExecutorSymbolDef::fromPtr(&__cxxabiv1::__cxa_throw);
Symbols[Intern("__cxa_begin_catch")] =
@@ -370,6 +388,37 @@ static llvm::Error defineRuntimeSymbols(llvm::orc::LLJIT &J) {
llvm::orc::absoluteSymbols(std::move(Symbols)));
}
+static llvm::Error loadCxxRuntime(llvm::orc::LLJIT &J) {
+ std::vector<std::unique_ptr<llvm::MemoryBuffer>> Archives;
+ if (!unpackCxxRuntime(Archives))
+ return llvm::make_error<llvm::StringError>("cannot unpack the C++ runtime", llvm::inconvertibleErrorCode());
+ for (auto &Archive : Archives) {
+ auto Generator = llvm::orc::StaticLibraryDefinitionGenerator::Create(
+ J.getObjLinkingLayer(), std::move(Archive),
+ [](llvm::object::Archive &, llvm::MemoryBufferRef Buffer, size_t) -> llvm::Expected<bool> {
+ auto Object = llvm::object::ObjectFile::createObjectFile(Buffer);
+ if (!Object) { llvm::consumeError(Object.takeError()); return false; }
+ // Keep one exception ABI and unwinder. ABI archive members must
+ // not replace the host addresses registered above.
+ for (const auto &Symbol : (*Object)->symbols()) {
+ auto Flags = Symbol.getFlags();
+ if (!Flags) return Flags.takeError();
+ if (*Flags & llvm::object::SymbolRef::SF_Undefined) continue;
+ auto Name = Symbol.getName();
+ if (!Name) return Name.takeError();
+ llvm::StringRef N = *Name;
+ if ((*Object)->isMachO() && N.starts_with("_")) N = N.drop_front();
+ if (N.starts_with("__cxa_") || N.starts_with("__gxx_personality_") ||
+ N.starts_with("_Unwind_") || N == "_ZTIi") return false;
+ }
+ return true;
+ });
+ if (!Generator) return Generator.takeError();
+ J.getMainJITDylib().addGenerator(std::move(*Generator));
+ }
+ return llvm::Error::success();
+}
+
int runEmbedded(const cps::CodeGen &CG, const std::vector<std::string> &Objects) {
if (llvm::InitializeNativeTarget() || llvm::InitializeNativeTargetAsmPrinter()) {
llvm::errs() << "error: cannot initialize the embedded LLVM target\n";
@@ -412,6 +461,11 @@ int runEmbedded(const cps::CodeGen &CG, const std::vector<std::string> &Objects)
<< llvm::toString(std::move(Err)) << "\n";
return 1;
}
+ if (auto Err = loadCxxRuntime(*J)) {
+ llvm::errs() << "error: cannot load embedded C++ runtime: "
+ << llvm::toString(std::move(Err)) << "\n";
+ return 1;
+ }
#ifdef _WIN32
// Process-wide lookup may find C functions in ucrtbase.dll, loaded by
// system DLLs, whose stdio buffers differ from the host's msvcrt. Prefer
diff --git a/tools/driver/NativeCompiler.cc b/tools/driver/NativeCompiler.cc
index 4547203..e09fe98 100644
--- a/tools/driver/NativeCompiler.cc
+++ b/tools/driver/NativeCompiler.cc
@@ -180,6 +180,26 @@ bool collectLibcSymbols(llvm::StringSet<> &Out) {
return true;
}
+bool unpackCxxRuntime(std::vector<std::unique_ptr<llvm::MemoryBuffer>> &Archives) {
+ llvm::json::Object *Metadata = nullptr;
+ std::optional<llvm::json::Value> Manifest;
+ llvm::SmallVector<uint8_t, 0> Data;
+ if (!unpackRuntime(Metadata, Manifest, Data)) return false;
+ for (const auto &Entry : *Metadata->getArray("files")) {
+ const auto &File = *Entry.getAsObject();
+ llvm::StringRef Path = *File.getString("path");
+ llvm::StringRef Name = llvm::sys::path::filename(Path);
+ // The unwind and C libraries continue to come from the host process.
+ if (*File.getBoolean("script") ||
+ !(Name == "libstdc++.a" || Name == "libc++.a" || Name == "libc++abi.a" ||
+ Name == "libgcc.a" || Name == "libmingwex.a" || Name == "libm.a")) continue;
+ llvm::StringRef Contents(reinterpret_cast<const char *>(Data.data()) +
+ *File.getInteger("offset"), *File.getInteger("size"));
+ Archives.push_back(llvm::MemoryBuffer::getMemBufferCopy(Contents, Path));
+ }
+ return true;
+}
+
bool compileNative(const cps::CodeGen &CG, llvm::StringRef OutputPath, const char *Argv0,
const std::vector<std::string> &Objects) {
TemporaryDirectory Runtime;
diff --git a/tools/driver/NativeCompiler.h b/tools/driver/NativeCompiler.h
index ffc38f1..54b690f 100644
--- a/tools/driver/NativeCompiler.h
+++ b/tools/driver/NativeCompiler.h
@@ -2,6 +2,8 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include <memory>
#include <string>
#include <vector>
@@ -19,3 +21,6 @@ bool collectObjectSymbols(llvm::StringRef Path, llvm::StringSet<> &Out, std::str
// Defined global symbols of the embedded C library (INCLUDE_OBJ "__libc").
bool collectLibcSymbols(llvm::StringSet<> &Out);
+
+// Owning copies of the embedded C++ and compiler-support static archives.
+bool unpackCxxRuntime(std::vector<std::unique_ptr<llvm::MemoryBuffer>> &Archives);