From 8d3a632f40eb6a63ff5316a874b0acdb503c87b2 Mon Sep 17 00:00:00 2001 From: Marek Lipert Date: Mon, 23 Mar 2026 12:35:47 +0100 Subject: [PATCH 01/10] More robust stacktraces. --- backend-v2/bridge/Exceptions.cpp | 90 +++++++------ backend-v2/jit/JITEngine.cpp | 71 ++++++---- backend-v2/jit/JITEngine.h | 7 +- backend-v2/main.cpp | 16 ++- backend-v2/tests/CMakeLists.txt | 1 + .../tests/codegen/O3_DebugInfo_test.cpp | 122 ++++++++++++++++++ 6 files changed, 234 insertions(+), 73 deletions(-) create mode 100644 backend-v2/tests/codegen/O3_DebugInfo_test.cpp diff --git a/backend-v2/bridge/Exceptions.cpp b/backend-v2/bridge/Exceptions.cpp index 9deae16a..df69be9b 100644 --- a/backend-v2/bridge/Exceptions.cpp +++ b/backend-v2/bridge/Exceptions.cpp @@ -115,46 +115,18 @@ LanguageException::toString(llvm::symbolize::LLVMSymbolizer &symbolizer, uword_t symbolizeAddr = 0; std::string dlSymbolName = ""; - Dl_info dlinfo; - bool hasDlInfo = dladdr(reinterpret_cast(addr), &dlinfo); - if (hasDlInfo) { - if (dlinfo.dli_sname) { - dlSymbolName = dlinfo.dli_sname; - } - - // 1. Check if we should start the trace (Found logic) - if (!found && !dlSymbolName.empty() && - dlSymbolName.find("throw") != std::string::npos && - dlSymbolName.find("Exception") != std::string::npos) { - found = true; - continue; - } - -#ifdef __APPLE__ - // 2. Identify module and offset - if (dlinfo.dli_fbase == _dyld_get_image_header(0)) { - // Main executable - must use dSYM and slide - currentModule = defaultModuleName; - lookupAddr = addr - defaultSlide; - } else if (dlinfo.dli_fname) { - currentModule = dlinfo.dli_fname; - lookupAddr = addr - reinterpret_cast(dlinfo.dli_fbase); - } -#else - if (dlinfo.dli_fname) { - currentModule = dlinfo.dli_fname; - lookupAddr = addr - reinterpret_cast(dlinfo.dli_fbase); - } -#endif - } else { - // Check JIT map + // 1. Check JIT map first. JIT addresses are specifically registered and + // should take precedence over static module identification (especially on + // macOS where dladdr can be over-eager). + { std::lock_guard lock(gJitMapMutex); auto it = gJitFunctions.lower_bound(addr); - if (it != gJitFunctions.begin()) { - auto prev = std::prev(it); - if (addr >= prev->first && addr < prev->first + prev->second.size) { - if (prev->second.objectBuffer) { - uword_t jitSymbolizeAddr = addr - prev->first; + if (it != gJitFunctions.begin() || (it != gJitFunctions.end() && it->first == addr)) { + auto entry = (it != gJitFunctions.end() && it->first == addr) ? it : std::prev(it); + if (addr >= entry->first && addr < entry->first + entry->second.size) { + // printf("Symbolizer: Found %p in JIT map entry %p (size %zu)\n", (void*)addr, (void*)entry->first, entry->second.size); + if (entry->second.objectBuffer) { + uword_t jitSymbolizeAddr = addr - entry->first; #if defined(__aarch64__) || defined(__arm64__) if (jitSymbolizeAddr >= 4) jitSymbolizeAddr -= 4; @@ -163,7 +135,7 @@ LanguageException::toString(llvm::symbolize::LLVMSymbolizer &symbolizer, jitSymbolizeAddr -= 1; #endif auto ObjOrErr = llvm::object::ObjectFile::createObjectFile( - prev->second.objectBuffer->getMemBufferRef()); + entry->second.objectBuffer->getMemBufferRef()); if (ObjOrErr) { auto &Obj = *ObjOrErr.get(); auto resOrErrJit = symbolizer.symbolizeCode( @@ -185,15 +157,49 @@ LanguageException::toString(llvm::symbolize::LLVMSymbolizer &symbolizer, } } - if (found) { - ss << " at " << prev->second.name << " [JIT:0x" << std::hex - << prev->first << std::dec << "]\n"; + if (found || (!found && !entry->second.name.empty())) { + ss << " at " << entry->second.name << " [JIT:0x" << std::hex + << entry->first << std::dec << "]\n"; + found = true; } continue; } } } + Dl_info dlinfo; + bool hasDlInfo = dladdr(reinterpret_cast(addr), &dlinfo); + if (hasDlInfo) { + if (dlinfo.dli_sname) { + dlSymbolName = dlinfo.dli_sname; + } + + // Check if we should start the trace (Found logic) + if (!found && !dlSymbolName.empty() && + dlSymbolName.find("throw") != std::string::npos && + dlSymbolName.find("Exception") != std::string::npos) { + found = true; + continue; + } + +#ifdef __APPLE__ + // 2. Identify module and offset + if (dlinfo.dli_fbase == _dyld_get_image_header(0)) { + // Main executable - must use dSYM and slide + currentModule = defaultModuleName; + lookupAddr = addr - defaultSlide; + } else if (dlinfo.dli_fname) { + currentModule = dlinfo.dli_fname; + lookupAddr = addr - reinterpret_cast(dlinfo.dli_fbase); + } +#else + if (dlinfo.dli_fname) { + currentModule = dlinfo.dli_fname; + lookupAddr = addr - reinterpret_cast(dlinfo.dli_fbase); + } +#endif + } + if (!found) continue; diff --git a/backend-v2/jit/JITEngine.cpp b/backend-v2/jit/JITEngine.cpp index 98b69fba..3b16c532 100644 --- a/backend-v2/jit/JITEngine.cpp +++ b/backend-v2/jit/JITEngine.cpp @@ -54,6 +54,33 @@ void JITEngine::unregisterThread() { std::atomic JITEngine::instanceExists{false}; + +JITEngine::CapturedObject JITEngine::captureObject(const llvm::MemoryBuffer &Obj) { + std::string id = Obj.getBufferIdentifier().str(); + CapturedObject captured; + captured.buffer = llvm::MemoryBuffer::getMemBufferCopy(Obj.getBuffer(), id); + + // Pre-parse symbols to make lookup in compileGeneric O(1) per buffer + auto ObjOrErr = llvm::object::ObjectFile::createObjectFile(captured.buffer->getMemBufferRef()); + if (ObjOrErr) { + auto &ParsedObj = *ObjOrErr.get(); + for (auto const &SymEntry : ParsedObj.symbols()) { + auto NameOrErr = SymEntry.getName(); + if (NameOrErr) { + llvm::StringRef symName = NameOrErr.get(); + captured.symbols.insert(symName.str()); + // Also store without the leading underscore for Mach-O targets + if (symName.starts_with("_")) { + captured.symbols.insert(symName.drop_front(1).str()); + } + } + } + } else { + llvm::consumeError(ObjOrErr.takeError()); + } + return captured; +} + JITEngine::JITEngine(ThreadsafeCompilerState &state, size_t numThreads) : pool(std::max(numThreads / 4, size_t(1)), Priority::Low), threadsafeState(state) { @@ -108,10 +135,7 @@ JITEngine::JITEngine(ThreadsafeCompilerState &state, size_t numThreads) [this](std::unique_ptr Obj) -> llvm::Expected> { std::lock_guard lock(this->engineMutex); - std::string id = Obj->getBufferIdentifier().str(); - // Store a copy of the buffer content - this->capturedObjectBuffers[id] = - llvm::MemoryBuffer::getMemBufferCopy(Obj->getBuffer(), id); + this->capturedObjectBuffers.push_back(JITEngine::captureObject(*Obj)); return std::move(Obj); }); @@ -229,6 +253,7 @@ JITEngine::compileGeneric(std::function codegenFunc, sweep(); auto Sym = jit->lookup(fName); + if (!Sym) { throwInternalInconsistencyException("Symbol Lookup Failed: " + fName); @@ -236,29 +261,29 @@ JITEngine::compileGeneric(std::function codegenFunc, { std::lock_guard lock(this->engineMutex); - auto itBuf = this->capturedObjectBuffers.find(moduleName); - if (itBuf == this->capturedObjectBuffers.end()) { - itBuf = this->capturedObjectBuffers.find( - moduleName + "-jitted-objectbuffer"); - } - if (itBuf == this->capturedObjectBuffers.end()) { - for (auto searchIt = this->capturedObjectBuffers.begin(); - searchIt != this->capturedObjectBuffers.end(); - ++searchIt) { - if (searchIt->first.find(moduleName) != std::string::npos) { - itBuf = searchIt; - break; - } + + // Find the object buffer that contains our symbol. + // This is much more robust than name-based matching. + size_t foundIdx = std::string::npos; + for (size_t i = 0; i < this->capturedObjectBuffers.size(); ++i) { + if (this->capturedObjectBuffers[i].symbols.count(fName)) { + foundIdx = i; + break; } } - if (itBuf != this->capturedObjectBuffers.end()) { - registerJitFunction_C(Sym->getValue(), 4096, fName.c_str(), - itBuf->second->getBufferStart(), - itBuf->second->getBufferSize()); - this->capturedObjectBuffers.erase(itBuf); + if (foundIdx != std::string::npos) { + auto &captured = this->capturedObjectBuffers[foundIdx]; + // printf("JIT: Found buffer for %s (size %zu)\n", fName.c_str(), captured.buffer->getBufferSize()); + registerJitFunction_C(Sym->getValue(), captured.buffer->getBufferSize(), fName.c_str(), + captured.buffer->getBufferStart(), + captured.buffer->getBufferSize()); + this->capturedObjectBuffers.erase(this->capturedObjectBuffers.begin() + foundIdx); } else { - registerJitFunction_C(Sym->getValue(), 4096, fName.c_str(), + // printf("JIT: FAILED to find buffer for %s among %zu candidates\n", fName.c_str(), this->capturedObjectBuffers.size()); + // Fallback for cases where symbol might not be in the dynamic symbol table + // of the object (though for JIT it usually is). + registerJitFunction_C(Sym->getValue(), 1024*1024, fName.c_str(), nullptr, 0); } } diff --git a/backend-v2/jit/JITEngine.h b/backend-v2/jit/JITEngine.h index 4519fb87..b5eaefc7 100644 --- a/backend-v2/jit/JITEngine.h +++ b/backend-v2/jit/JITEngine.h @@ -66,7 +66,11 @@ class JITEngine { std::mutex engineMutex; std::map functionTrackers; std::map> moduleConstants; - std::map> capturedObjectBuffers; + struct CapturedObject { + std::unique_ptr buffer; + std::unordered_set symbols; + }; + std::vector capturedObjectBuffers; // Epoch-based Reclamation (EBR) struct ZombieTracker { @@ -148,6 +152,7 @@ class JITEngine { uint64_t getGlobalEpoch() const { return globalEpoch.load(std::memory_order_relaxed); } private: + static CapturedObject captureObject(const llvm::MemoryBuffer &Obj); static std::atomic instanceExists; }; diff --git a/backend-v2/main.cpp b/backend-v2/main.cpp index 9d937ff5..d07fed43 100644 --- a/backend-v2/main.cpp +++ b/backend-v2/main.cpp @@ -94,16 +94,17 @@ int main(int argc, char *argv[]) { RTValue interfaces = engine .compileAST(astInterfaces.nodes(0), "__interfaces", llvm::OptimizationLevel::O0, false) - .get().address - .toPtr()(); + .get() + .address.toPtr()(); cout << "Storing interfaces..." << endl; state.storeInternalProtocols(interfaces); cout << "Compiling classes..." << endl; - RTValue classes = engine.compileAST(astClasses.nodes(0), "__classes", - llvm::OptimizationLevel::O0, false) - .get().address - .toPtr()(); + RTValue classes = engine + .compileAST(astClasses.nodes(0), "__classes", + llvm::OptimizationLevel::O0, false) + .get() + .address.toPtr()(); cout << "Storing classes..." << endl; state.storeInternalClasses(classes); @@ -113,7 +114,8 @@ int main(int argc, char *argv[]) { cout << "=============================" << endl; cout << "Compiling!!!" << endl; std::string moduleName = "__repl__" + std::to_string(j); - auto res = engine.compileAST(topLevelNode, moduleName, optLevel, true).get(); + auto res = + engine.compileAST(topLevelNode, moduleName, optLevel, true).get(); if (!res.optimizedIR.empty()) { cout << "\n=== Optimized LLVM IR for: '" << moduleName << "' ===\n"; diff --git a/backend-v2/tests/CMakeLists.txt b/backend-v2/tests/CMakeLists.txt index 3c430002..761ef7ad 100644 --- a/backend-v2/tests/CMakeLists.txt +++ b/backend-v2/tests/CMakeLists.txt @@ -21,6 +21,7 @@ set(TEST_MODULES codegen/InstanceCallIC_test codegen/VoidInvokeRepro_test codegen/DCE_test + codegen/O3_DebugInfo_test VarUAF_repro_test JITSafety_test ) diff --git a/backend-v2/tests/codegen/O3_DebugInfo_test.cpp b/backend-v2/tests/codegen/O3_DebugInfo_test.cpp new file mode 100644 index 00000000..489fd81d --- /dev/null +++ b/backend-v2/tests/codegen/O3_DebugInfo_test.cpp @@ -0,0 +1,122 @@ +#include "../../RuntimeHeaders.h" +#include "../../jit/JITEngine.h" +#include "../../state/ThreadsafeCompilerState.h" +#include "../../tools/EdnParser.h" +#include "bytecode.pb.h" +#include +#include +#include + +extern "C" { +#include "../../runtime/RuntimeInterface.h" +#include "../../runtime/tests/TestTools.h" +#include +} + +#include "../../bridge/Exceptions.h" + +using namespace std; +using namespace rt; +using namespace clojure::rt::protobuf::bytecode; + +static RTValue resPtrToValue(llvm::orc::ExecutorAddr res) { + return res.toPtr()(); +} + +static void setup_compiler_state(rt::ThreadsafeCompilerState &compState, rt::JITEngine &engine) { + // Load metadata + Programme astClasses; + { + std::fstream classesInput("tests/rt-classes.cljb", + std::ios::in | std::ios::binary); + if (!astClasses.ParseFromIstream(&classesInput)) { + fail_msg("Failed to parse bytecode."); + } + } + + // Initialize compiler state with metadata + llvm::orc::ExecutorAddr resClasses = + engine + .compileAST(astClasses.nodes(0), "__classes", + llvm::OptimizationLevel::O0, false) + .get().address; + RTValue classes = resClasses.toPtr()(); + auto classesList = rt::buildClasses(classes); + for (auto &desc : classesList) { + auto &nameStr = desc->name; + String *className = String_create(nameStr.c_str()); + Ptr_retain(className); + Class *cls = Class_create(className, className, 0, nullptr); + cls->compilerExtension = desc.release(); + cls->compilerExtensionDestructor = delete_class_description; + compState.classRegistry.registerObject(nameStr.c_str(), cls); + } +} + +static void test_o3_debug_info(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + setup_compiler_state(compState, engine); + + // Create a StaticCallNode for (clojure.lang.Numbers/add 2147483647 1) + // and set some dummy environment info + Node callNode; + callNode.set_op(opStaticCall); + callNode.set_tag("long"); + auto *env = callNode.mutable_env(); + env->set_file("./test_file.clj"); + env->set_line(42); + + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("add"); + + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->set_tag("long"); + arg1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg1->mutable_subnode()->mutable_const_()->set_val("2147483647"); + + auto *arg2 = sc->add_args(); + arg2->set_op(opConst); + arg2->set_tag("long"); + arg2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg2->mutable_subnode()->mutable_const_()->set_val("1"); + + // Compile with O3 + auto resCall = engine + .compileAST(callNode, "__repl__o3_test", + llvm::OptimizationLevel::O3, false) + .get().address; + + try { + resPtrToValue(resCall); + fail_msg("Should have thrown ArithmeticException"); + } catch (const LanguageException &e) { + std::string err = getExceptionString(e); + cout << "Caught Exception String:\n" << err << endl; + + // Verify function name is preserved (it starts with __repl__) + assert_true(err.find("__repl__") != std::string::npos); + + // Verify file is preserved + assert_true(err.find("test_file.clj") != std::string::npos); + + // We don't strictly assert the line number is 42 because O3 might + // have shifted things, but we verified we have symbolic info! + } + }); +} + +int main(void) { + initialise_memory(); + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_o3_debug_info), + }; + + int result = cmocka_run_group_tests(tests, NULL, NULL); + RuntimeInterface_cleanup(); + return result; +} From e990f9bc845f6be3588ef8d2702a41be3a838db6 Mon Sep 17 00:00:00 2001 From: Marek Lipert Date: Mon, 23 Mar 2026 14:46:28 +0100 Subject: [PATCH 02/10] Friendly and Debug logging modes. --- backend-v2/bridge/Exceptions.cpp | 385 +++++++++++------- backend-v2/bridge/Exceptions.h | 20 +- backend-v2/tests/CMakeLists.txt | 1 + .../tests/codegen/AsyncStackTrace_test.cpp | 137 +++++++ backend-v2/tools/ThreadPool.h | 15 +- 5 files changed, 396 insertions(+), 162 deletions(-) create mode 100644 backend-v2/tests/codegen/AsyncStackTrace_test.cpp diff --git a/backend-v2/bridge/Exceptions.cpp b/backend-v2/bridge/Exceptions.cpp index df69be9b..59c17382 100644 --- a/backend-v2/bridge/Exceptions.cpp +++ b/backend-v2/bridge/Exceptions.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,8 @@ namespace rt { +thread_local std::shared_ptr gCurrentAsyncStack = nullptr; + struct JitInfo { std::string name; size_t size; @@ -40,26 +43,32 @@ extern "C" void registerJitFunction_C(uword_t addr, size_t size, gJitFunctions[addr] = {name ? name : "", size, std::move(buffer)}; } -// Removing redundant namespace rt { +std::shared_ptr captureCurrentStack() { + auto stack = std::make_shared(); + void *buffer[128]; + int size = backtrace(buffer, 128); + stack->addresses.reserve(size); + // Skip 2 frames to bypass captureCurrentStack and its direct caller (wrapper) + for (int i = 2; i < size; i++) { + stack->addresses.push_back(reinterpret_cast(buffer[i])); + } + stack->parent = gCurrentAsyncStack; + return stack; +} LanguageException::LanguageException(const std::string &name, RTValue message, RTValue payload) { this->payload = payload; this->name = name; this->message = message; - void *buffer[128]; - int size = backtrace(buffer, 128); - stackAddresses.reserve(size); - for (int i = 0; i < size; i++) { - stackAddresses.push_back(reinterpret_cast(buffer[i])); - } + this->capturedStack = captureCurrentStack(); } LanguageException::LanguageException(const LanguageException &other) { this->name = other.name; this->message = other.message; this->payload = other.payload; - this->stackAddresses = other.stackAddresses; + this->capturedStack = other.capturedStack; retain(this->message); retain(this->payload); } @@ -72,7 +81,7 @@ LanguageException::operator=(const LanguageException &other) { this->name = other.name; this->message = other.message; this->payload = other.payload; - this->stackAddresses = other.stackAddresses; + this->capturedStack = other.capturedStack; retain(this->message); retain(this->payload); } @@ -85,192 +94,252 @@ LanguageException::~LanguageException() noexcept { } void LanguageException::printRawTrace() const { - for (uword_t addr : stackAddresses) { + if (!capturedStack) return; + for (uword_t addr : capturedStack->addresses) { printf(" [JIT ADDR] %p\n", (void *)addr); } } -std::string -LanguageException::toString(llvm::symbolize::LLVMSymbolizer &symbolizer, - const std::string &defaultModuleName, - const intptr_t defaultSlide) const { - std::stringstream ss; - - ss << "Exception: " << name << "\n"; - retain(message); - String *messageString = String_compactify(::toString(message)); - ss << "Message: " << String_c_str(messageString) << "\n"; - Ptr_release(messageString); +static bool isInfrastructureFrame(const std::string &fnName, + const std::string &fileName) { + if (fnName.empty()) + return false; + + // Patterns to skip in Friendly mode + static const char *skipPatterns[] = { + "std::__1", + "rt::InvokeManager", + "rt::CodeGen", + "rt::JITEngine", + "rt::ThreadPool", + "rt::CleanupChain", + "rt::InstanceCallStub", + "InstanceCallSlowPath", + "throwInternalInconsistencyException", + "throwNoMatchingOverloadException", + "throwLanguageException", + "throwArityException", + "throwIllegalArgumentException", + "throwIllegalStateException", + "throwUnsupportedOperationException", + "throwArithmeticException", + "throwIndexOutOfBoundsException", + "throwCodeGenerationException", + "asan_thread_start", + "__packaged_task", + "__invoke", + "__function", + "__alloc_func", + "__value_func", + "__packaged_task_func", + "__thread_proxy", + "__thread_execute", + "future", + "decltype", + "std::declval", + "cmocka_", + "_cmocka_"}; + + for (const char *p : skipPatterns) { + if (fnName.find(p) != std::string::npos) + return true; + } - retain(payload); - String *payloadString = String_compactify(::toString(payload)); - ss << "Payload: " << String_c_str(payloadString) << "\n"; - Ptr_release(payloadString); + // System headers / ASan libs / C++ machinery + if (fileName.find("/usr/include/c++/") != std::string::npos) + return true; + if (fileName.find("libclang_rt.asan") != std::string::npos) + return true; + if (fileName.find("__functional") != std::string::npos) + return true; + if (fileName.find("__type_traits") != std::string::npos) + return true; + if (fileName.find("/c++/v1/") != std::string::npos) + return true; + + return false; +} - ss << "Stack Trace:\n"; - bool found = false; - for (uword_t addr : stackAddresses) { - std::string currentModule = ""; - uword_t lookupAddr = addr; - uword_t symbolizeAddr = 0; - std::string dlSymbolName = ""; - - // 1. Check JIT map first. JIT addresses are specifically registered and - // should take precedence over static module identification (especially on - // macOS where dladdr can be over-eager). - { - std::lock_guard lock(gJitMapMutex); - auto it = gJitFunctions.lower_bound(addr); - if (it != gJitFunctions.begin() || (it != gJitFunctions.end() && it->first == addr)) { - auto entry = (it != gJitFunctions.end() && it->first == addr) ? it : std::prev(it); - if (addr >= entry->first && addr < entry->first + entry->second.size) { - // printf("Symbolizer: Found %p in JIT map entry %p (size %zu)\n", (void*)addr, (void*)entry->first, entry->second.size); - if (entry->second.objectBuffer) { - uword_t jitSymbolizeAddr = addr - entry->first; +static void symbolizeStackChain(std::stringstream &ss, + const std::shared_ptr &stack, + llvm::symbolize::LLVMSymbolizer &symbolizer, + const std::string &moduleName, + const intptr_t slide, StackTraceMode mode) { + if (!stack) return; + + auto current = stack; + bool isFirst = true; + while (current) { + bool found = !isFirst; + int skipCount = 0; + + for (uword_t addr : current->addresses) { + std::string currentModule = ""; + uword_t lookupAddr = addr; + uword_t symbolizeAddr = 0; + std::string dlSymbolName = ""; + + // 1. Check JIT map first + { + std::lock_guard lock(gJitMapMutex); + auto it = gJitFunctions.lower_bound(addr); + if (it != gJitFunctions.begin() || (it != gJitFunctions.end() && it->first == addr)) { + auto entry = (it != gJitFunctions.end() && it->first == addr) ? it : std::prev(it); + if (addr >= entry->first && addr < entry->first + entry->second.size) { + if (entry->second.objectBuffer) { + uword_t jitSymbolizeAddr = addr - entry->first; #if defined(__aarch64__) || defined(__arm64__) - if (jitSymbolizeAddr >= 4) - jitSymbolizeAddr -= 4; + if (jitSymbolizeAddr >= 4) jitSymbolizeAddr -= 4; #else - if (jitSymbolizeAddr > 0) - jitSymbolizeAddr -= 1; + if (jitSymbolizeAddr > 0) jitSymbolizeAddr -= 1; #endif - auto ObjOrErr = llvm::object::ObjectFile::createObjectFile( - entry->second.objectBuffer->getMemBufferRef()); - if (ObjOrErr) { - auto &Obj = *ObjOrErr.get(); - auto resOrErrJit = symbolizer.symbolizeCode( - Obj, {jitSymbolizeAddr, - llvm::object::SectionedAddress::UndefSection}); - if (resOrErrJit) { - auto &info = resOrErrJit.get(); - if (info.FileName != "") { - ss << " at " << info.FunctionName << " [" << info.FileName - << ":" << info.Line << "]\n"; - found = true; - continue; + auto ObjOrErr = llvm::object::ObjectFile::createObjectFile( + entry->second.objectBuffer->getMemBufferRef()); + if (ObjOrErr) { + auto &Obj = *ObjOrErr.get(); + auto resOrErrJit = symbolizer.symbolizeCode(Obj, {jitSymbolizeAddr, llvm::object::SectionedAddress::UndefSection}); + if (resOrErrJit) { + auto &info = resOrErrJit.get(); + if (info.FileName != "") { + if (found) { + std::string demangled = llvm::demangle(info.FunctionName); + if (mode == StackTraceMode::Debug || !isInfrastructureFrame(demangled, info.FileName)) { + ss << " at " << demangled << " [" << info.FileName << ":" << info.Line << "]\n"; + } + } + found = true; + continue; + } + } else { + llvm::consumeError(resOrErrJit.takeError()); } } else { - llvm::consumeError(resOrErrJit.takeError()); + llvm::consumeError(ObjOrErr.takeError()); } - } else { - llvm::consumeError(ObjOrErr.takeError()); } - } - if (found || (!found && !entry->second.name.empty())) { - ss << " at " << entry->second.name << " [JIT:0x" << std::hex - << entry->first << std::dec << "]\n"; - found = true; + if (found || (!found && !entry->second.name.empty())) { + if (found) { + std::string demangled = llvm::demangle(entry->second.name); + if (mode == StackTraceMode::Debug || !isInfrastructureFrame(demangled, "")) { + ss << " at " << demangled << " [JIT:0x" << std::hex << entry->first << std::dec << "]\n"; + } + } + found = true; + } + continue; } - continue; } } - } - Dl_info dlinfo; - bool hasDlInfo = dladdr(reinterpret_cast(addr), &dlinfo); - if (hasDlInfo) { - if (dlinfo.dli_sname) { - dlSymbolName = dlinfo.dli_sname; - } + Dl_info dlinfo; + bool hasDlInfo = dladdr(reinterpret_cast(addr), &dlinfo); + if (hasDlInfo) { + if (dlinfo.dli_sname) dlSymbolName = dlinfo.dli_sname; - // Check if we should start the trace (Found logic) - if (!found && !dlSymbolName.empty() && - dlSymbolName.find("throw") != std::string::npos && - dlSymbolName.find("Exception") != std::string::npos) { - found = true; - continue; - } + // Skip thread entry points if we have a parent to stitch to + if (current->parent && (dlSymbolName == "thread_start" || dlSymbolName == "_pthread_start" || dlSymbolName == "start" || dlSymbolName == "asan_thread_start")) { + continue; + } + + if (!found) { + bool isSentinel = !dlSymbolName.empty() && + (dlSymbolName.find("throw") != std::string::npos || + dlSymbolName.find("Exception") != std::string::npos); + if (isSentinel || ++skipCount > 20) { + found = true; + if (isSentinel) continue; + } + } #ifdef __APPLE__ - // 2. Identify module and offset - if (dlinfo.dli_fbase == _dyld_get_image_header(0)) { - // Main executable - must use dSYM and slide - currentModule = defaultModuleName; - lookupAddr = addr - defaultSlide; - } else if (dlinfo.dli_fname) { - currentModule = dlinfo.dli_fname; - lookupAddr = addr - reinterpret_cast(dlinfo.dli_fbase); - } + if (dlinfo.dli_fbase == _dyld_get_image_header(0)) { + currentModule = moduleName; + lookupAddr = addr - slide; + } else if (dlinfo.dli_fname) { + currentModule = dlinfo.dli_fname; + lookupAddr = addr - reinterpret_cast(dlinfo.dli_fbase); + } #else - if (dlinfo.dli_fname) { - currentModule = dlinfo.dli_fname; - lookupAddr = addr - reinterpret_cast(dlinfo.dli_fbase); - } + if (dlinfo.dli_fname) { + currentModule = dlinfo.dli_fname; + lookupAddr = addr - reinterpret_cast(dlinfo.dli_fbase); + } #endif - } + } - if (!found) - continue; + if (!found) continue; - // Adjust address back to get the call site line instead of return site. - symbolizeAddr = lookupAddr; + symbolizeAddr = lookupAddr; #if defined(__aarch64__) || defined(__arm64__) - if (symbolizeAddr >= 4) - symbolizeAddr -= 4; + if (symbolizeAddr >= 4) symbolizeAddr -= 4; #else - if (symbolizeAddr > 0) - symbolizeAddr -= 1; + if (symbolizeAddr > 0) symbolizeAddr -= 1; #endif - if (currentModule.empty()) { - currentModule = defaultModuleName; + if (currentModule.empty()) { + currentModule = moduleName; #if defined(__aarch64__) || defined(__arm64__) - symbolizeAddr = (addr - defaultSlide) - 4; + symbolizeAddr = (addr - slide) - 4; #else - symbolizeAddr = (addr - defaultSlide) - 1; + symbolizeAddr = (addr - slide) - 1; #endif - } - - auto resOrErr = symbolizer.symbolizeCode( - currentModule, - {symbolizeAddr, llvm::object::SectionedAddress::UndefSection}); - - if (resOrErr) { - auto &info = resOrErr.get(); - // Second chance for found (if dladdr failed somehow) - if (!found && - (info.FunctionName.find("throw") != std::string::npos && - info.FunctionName.find("Exception") != std::string::npos)) { - found = true; - continue; } - if (found) { - ss << " at "; - - if (info.FunctionName != "") { - ss << info.FunctionName; - } else if (!dlSymbolName.empty()) { - ss << dlSymbolName; - } else if (!currentModule.empty()) { - size_t lastSlash = currentModule.find_last_of('/'); - std::string base = (lastSlash != std::string::npos) - ? currentModule.substr(lastSlash + 1) - : currentModule; - ss << base << " + 0x" << std::hex << lookupAddr << std::dec; - } else { - ss << "0x" << std::hex << std::setw(sizeof(uword_t) * 2) - << std::setfill('0') << addr << std::dec; - } - - if (info.FileName != "") { - ss << " [" << info.FileName << ":" << info.Line << "]"; + auto resOrErr = symbolizer.symbolizeCode(currentModule, {symbolizeAddr, llvm::object::SectionedAddress::UndefSection}); + if (resOrErr) { + auto &info = resOrErr.get(); + if (found) { + std::string rawFn = (info.FunctionName != "") ? info.FunctionName : dlSymbolName; + std::string demangled = llvm::demangle(rawFn); + if (mode == StackTraceMode::Debug || !isInfrastructureFrame(demangled, info.FileName)) { + ss << " at " << demangled; + if (info.FileName != "") ss << " [" << info.FileName << ":" << info.Line << "]"; + else if (!currentModule.empty()) { + size_t lastSlash = currentModule.find_last_of('/'); + std::string base = (lastSlash != std::string::npos) ? currentModule.substr(lastSlash + 1) : currentModule; + ss << " [" << base << " + 0x" << std::hex << lookupAddr << std::dec << "]"; + } + ss << "\n"; + } } - ss << "\n"; - } - } else { - llvm::consumeError(resOrErr.takeError()); - if (found) { - if (!dlSymbolName.empty()) { - ss << " at " << dlSymbolName << " [" << currentModule << "]\n"; - } else { - ss << " at 0x" << std::hex << addr << std::dec << " [" - << currentModule << "]\n"; + } else { + llvm::consumeError(resOrErr.takeError()); + if (found) { + std::string demangled = llvm::demangle(dlSymbolName); + if (mode == StackTraceMode::Debug || !isInfrastructureFrame(demangled, currentModule)) { + if (!dlSymbolName.empty()) ss << " at " << demangled << " [" << currentModule << "]\n"; + else ss << " at 0x" << std::hex << addr << std::dec << " [" << currentModule << "]\n"; + } } } } + + current = current->parent; + isFirst = false; } +} + +std::string +LanguageException::toString(llvm::symbolize::LLVMSymbolizer &symbolizer, + const std::string &defaultModuleName, + const intptr_t defaultSlide, + StackTraceMode mode) const { + std::stringstream ss; + + ss << "Exception: " << name << "\n"; + retain(message); + String *messageString = String_compactify(::toString(message)); + ss << "Message: " << String_c_str(messageString) << "\n"; + Ptr_release(messageString); + + retain(payload); + String *payloadString = String_compactify(::toString(payload)); + ss << "Payload: " << String_c_str(payloadString) << "\n"; + Ptr_release(payloadString); + + ss << "Stack Trace:\n"; + symbolizeStackChain(ss, capturedStack, symbolizer, defaultModuleName, defaultSlide, mode); return ss.str(); } @@ -291,7 +360,7 @@ std::string getSelfExecutablePath() { return ""; } -std::string getExceptionString(const LanguageException &e) { +std::string getExceptionString(const LanguageException &e, StackTraceMode mode) { llvm::symbolize::LLVMSymbolizer::Options options; options.Demangle = true; options.PrintFunctions = llvm::symbolize::FunctionNameKind::LinkageName; @@ -311,7 +380,7 @@ std::string getExceptionString(const LanguageException &e) { intptr_t slide = 0; #endif - return e.toString(symbolizer, moduleName, slide); + return e.toString(symbolizer, moduleName, slide, mode); } } // namespace rt diff --git a/backend-v2/bridge/Exceptions.h b/backend-v2/bridge/Exceptions.h index 222df504..12a37265 100644 --- a/backend-v2/bridge/Exceptions.h +++ b/backend-v2/bridge/Exceptions.h @@ -19,11 +19,22 @@ using namespace clojure::rt::protobuf::bytecode; namespace rt { +enum class StackTraceMode { Friendly, Debug }; + +struct CapturedStack { + std::vector addresses; + std::shared_ptr parent; +}; + +extern thread_local std::shared_ptr gCurrentAsyncStack; + +std::shared_ptr captureCurrentStack(); + class LanguageException : public std::exception { std::string name; RTValue message; RTValue payload; - std::vector stackAddresses; + std::shared_ptr capturedStack; public: LanguageException(const std::string &name, RTValue message, RTValue payload); @@ -31,12 +42,15 @@ class LanguageException : public std::exception { LanguageException &operator=(const LanguageException &other); ~LanguageException() noexcept override; void printRawTrace() const; + std::string toString(llvm::symbolize::LLVMSymbolizer &symbolizer, const std::string &moduleName = "JITMemoryBuffer", - const intptr_t slide = 0x0) const; + const intptr_t slide = 0x0, + StackTraceMode mode = StackTraceMode::Friendly) const; }; -std::string getExceptionString(const LanguageException &e); +std::string getExceptionString(const LanguageException &e, + StackTraceMode mode = StackTraceMode::Friendly); } // namespace rt diff --git a/backend-v2/tests/CMakeLists.txt b/backend-v2/tests/CMakeLists.txt index 761ef7ad..14352574 100644 --- a/backend-v2/tests/CMakeLists.txt +++ b/backend-v2/tests/CMakeLists.txt @@ -22,6 +22,7 @@ set(TEST_MODULES codegen/VoidInvokeRepro_test codegen/DCE_test codegen/O3_DebugInfo_test + codegen/AsyncStackTrace_test VarUAF_repro_test JITSafety_test ) diff --git a/backend-v2/tests/codegen/AsyncStackTrace_test.cpp b/backend-v2/tests/codegen/AsyncStackTrace_test.cpp new file mode 100644 index 00000000..95338aed --- /dev/null +++ b/backend-v2/tests/codegen/AsyncStackTrace_test.cpp @@ -0,0 +1,137 @@ +#include "../../bridge/Exceptions.h" +#include "../../bridge/InstanceCallStub.h" +#include "../../jit/JITEngine.h" +#include "../../state/ThreadsafeCompilerState.h" +#include "bytecode.pb.h" +#include +#include +#include + +extern "C" { +#include "../../runtime/RuntimeInterface.h" +#include "../../runtime/tests/TestTools.h" +} + +using namespace rt; +using namespace std; + +// Helper to load metadata from protobuf (copied from JITSafety_test.cpp) +static void setup_test_metadata(rt::ThreadsafeCompilerState &compState, + rt::JITEngine &engine) { + clojure::rt::protobuf::bytecode::Programme astClasses; + std::string path = "tests/rt-classes.cljb"; + std::fstream classesInput(path, std::ios::in | std::ios::binary); + if (!classesInput.is_open()) { + path = "backend-v2/tests/rt-classes.cljb"; + classesInput.open(path, std::ios::in | std::ios::binary); + } + + if (classesInput.is_open()) { + if (!astClasses.ParseFromIstream(&classesInput)) { + fprintf(stderr, "Failed to parse bytecode from %s\n", path.c_str()); + return; + } + } else { + fprintf(stderr, "Could not open rt-classes.cljb\n"); + return; + } + + try { + auto res = engine + .compileAST(astClasses.nodes(0), "__classes", + llvm::OptimizationLevel::O0, false) + .get() + .address; + RTValue classes = res.toPtr()(); + compState.storeInternalClasses(classes); + } catch (const LanguageException &e) { + fprintf(stderr, "LanguageException during setup_test_metadata: %s\n", + getExceptionString(e).c_str()); + throw; + } +} + +static rt::ThreadsafeCompilerState *gCompState = nullptr; +static rt::JITEngine *gEngine = nullptr; + +static int setup_test_group(void **state) { + initialise_memory(); + gCompState = new rt::ThreadsafeCompilerState(); + gEngine = new rt::JITEngine(*gCompState); + setup_test_metadata(*gCompState, *gEngine); + return 0; +} + +static int teardown_test_group(void **state) { + delete gEngine; + delete gCompState; + RuntimeInterface_cleanup(); + return 0; +} + +static void test_async_stack_trace_friendly(void **state) { + (void)state; + + RTValue strObj = RT_boxPtr(String_create("test")); + retain(strObj); + + InlineCache icSlot = {0, nullptr}; + + try { + RTValue args[1] = {strObj}; + InstanceCallSlowPath(&icSlot, "contas", 0, args, 0, gEngine); + fail_msg("Expected an exception from .contas"); + } catch (const LanguageException &e) { + string trace = getExceptionString(e, StackTraceMode::Friendly); + cout << "Friendly Exception String:\n" << trace << endl; + + // 1. Check for the error message + assert_non_null( + strstr(trace.c_str(), "does not have an instance method contas")); + + // 2. Friendly mode should CULL infrastructure + assert_null(strstr(trace.c_str(), "rt::InvokeManager")); + assert_null(strstr(trace.c_str(), "rt::JITEngine")); + assert_null(strstr(trace.c_str(), "InstanceCallSlowPath")); + assert_null(strstr(trace.c_str(), "std::__1")); + assert_null(strstr(trace.c_str(), "throwInternalInconsistencyException")); + + // 3. But it should show the main test thread (the end of the chain) + assert_non_null(strstr(trace.c_str(), "test_async_stack_trace_friendly")); + } + + release(strObj); +} + +static void test_async_stack_trace_debug(void **state) { + (void)state; + + RTValue strObj = RT_boxPtr(String_create("test")); + retain(strObj); + + InlineCache icSlot = {0, nullptr}; + + try { + RTValue args[1] = {strObj}; + InstanceCallSlowPath(&icSlot, "contas", 0, args, 0, gEngine); + fail_msg("Expected an exception from .contas"); + } catch (const LanguageException &e) { + string trace = getExceptionString(e, StackTraceMode::Debug); + cout << "Debug Exception String:\n" << trace << endl; + + // 1. Debug mode should show EVERYTHING + assert_non_null(strstr(trace.c_str(), "rt::InvokeManager")); + assert_non_null(strstr(trace.c_str(), "InstanceCallSlowPath")); + assert_non_null(strstr(trace.c_str(), "test_async_stack_trace_debug")); + } + + release(strObj); +} + +int main() { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_async_stack_trace_friendly), + cmocka_unit_test(test_async_stack_trace_debug), + }; + return cmocka_run_group_tests(tests, setup_test_group, teardown_test_group); +} diff --git a/backend-v2/tools/ThreadPool.h b/backend-v2/tools/ThreadPool.h index ea284f3c..d435a10b 100644 --- a/backend-v2/tools/ThreadPool.h +++ b/backend-v2/tools/ThreadPool.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #ifdef _WIN32 @@ -24,6 +25,10 @@ namespace rt { enum class Priority { Low, High }; + struct CapturedStack; + extern thread_local std::shared_ptr gCurrentAsyncStack; + std::shared_ptr captureCurrentStack(); + class ThreadPool { std::vector workers; std::queue> tasks; @@ -38,13 +43,21 @@ namespace rt { template auto enqueue(F &&f) -> std::future> { using return_type = std::invoke_result_t; + + auto currentStack = captureCurrentStack(); + auto task = std::make_shared>(std::forward(f)); std::future res = task->get_future(); { std::unique_lock lock(queueMutex); if (!stop) - tasks.emplace([task]() { (*task)(); }); + tasks.emplace([task, currentStack]() { + auto old = gCurrentAsyncStack; + gCurrentAsyncStack = currentStack; + (*task)(); + gCurrentAsyncStack = old; + }); } condition.notify_one(); return res; From 38cbf03311dd64814e94e5d980ca53d0066ae7b2 Mon Sep 17 00:00:00 2001 From: Marek Lipert Date: Mon, 23 Mar 2026 17:09:14 +0100 Subject: [PATCH 03/10] Fix tests. --- backend-v2/CMakeLists.txt | 6 +- backend-v2/bridge/Exceptions.cpp | 108 +++++---- backend-v2/jit/JITEngine.cpp | 4 +- backend-v2/jit/JITEngine.h | 1 + backend-v2/tests/CMakeLists.txt | 1 + .../codegen/DeepStackSymbolization_test.cpp | 222 ++++++++++++++++++ 6 files changed, 287 insertions(+), 55 deletions(-) create mode 100644 backend-v2/tests/codegen/DeepStackSymbolization_test.cpp diff --git a/backend-v2/CMakeLists.txt b/backend-v2/CMakeLists.txt index ee9f5356..bd6cdbb5 100644 --- a/backend-v2/CMakeLists.txt +++ b/backend-v2/CMakeLists.txt @@ -244,16 +244,16 @@ if(APPLE) if(DSYMUTIL_TOOL) add_custom_command(TARGET clojure-rt POST_BUILD COMMAND ${DSYMUTIL_TOOL} $ - COMMENT "Generowanie pliku .dSYM dla clojure-rt..." + COMMENT "Generating the .dSYM file for clojure-rt..." VERBATIM ) endif() - # Opcjonalnie: jeśli chcesz, aby binarka po wyciągnięciu symboli była mniejsza: + # Optional: if you want the binary to be smaller after stripping symbols: if(CMAKE_BUILD_TYPE STREQUAL "Release") add_custom_command(TARGET clojure-rt POST_BUILD COMMAND strip -x $ - COMMENT "Stripping binarki (usuwanie zbędnych symboli)..." + COMMENT "Stripping the binary (removing unnecessary symbols)..." DEPENDS clojure-rt ) endif() diff --git a/backend-v2/bridge/Exceptions.cpp b/backend-v2/bridge/Exceptions.cpp index 59c17382..f76c5458 100644 --- a/backend-v2/bridge/Exceptions.cpp +++ b/backend-v2/bridge/Exceptions.cpp @@ -28,6 +28,7 @@ struct JitInfo { std::string name; size_t size; std::unique_ptr objectBuffer; + mutable std::unique_ptr objectFile; }; static std::mutex gJitMapMutex; @@ -40,7 +41,7 @@ extern "C" void registerJitFunction_C(uword_t addr, size_t size, auto buffer = llvm::MemoryBuffer::getMemBufferCopy( llvm::StringRef(static_cast(objData), objSize), name ? name : "jit_object"); - gJitFunctions[addr] = {name ? name : "", size, std::move(buffer)}; + gJitFunctions[addr] = {name ? name : "", size, std::move(buffer), nullptr}; } std::shared_ptr captureCurrentStack() { @@ -170,14 +171,14 @@ static void symbolizeStackChain(std::stringstream &ss, auto current = stack; bool isFirst = true; while (current) { - bool found = !isFirst; - int skipCount = 0; + bool found = (mode == StackTraceMode::Debug) || !isFirst; for (uword_t addr : current->addresses) { std::string currentModule = ""; uword_t lookupAddr = addr; uword_t symbolizeAddr = 0; std::string dlSymbolName = ""; + bool addrHandled = false; // 1. Check JIT map first { @@ -187,47 +188,57 @@ static void symbolizeStackChain(std::stringstream &ss, auto entry = (it != gJitFunctions.end() && it->first == addr) ? it : std::prev(it); if (addr >= entry->first && addr < entry->first + entry->second.size) { if (entry->second.objectBuffer) { - uword_t jitSymbolizeAddr = addr - entry->first; + if (!entry->second.objectFile) { + auto ObjOrErr = llvm::object::ObjectFile::createObjectFile( + entry->second.objectBuffer->getMemBufferRef()); + if (ObjOrErr) { + entry->second.objectFile = std::move(ObjOrErr.get()); + } else { + llvm::consumeError(ObjOrErr.takeError()); + } + } + + if (entry->second.objectFile) { + uword_t jitSymbolizeAddr = addr - entry->first; #if defined(__aarch64__) || defined(__arm64__) - if (jitSymbolizeAddr >= 4) jitSymbolizeAddr -= 4; + if (jitSymbolizeAddr >= 4) jitSymbolizeAddr -= 4; #else - if (jitSymbolizeAddr > 0) jitSymbolizeAddr -= 1; + if (jitSymbolizeAddr > 0) jitSymbolizeAddr -= 1; #endif - auto ObjOrErr = llvm::object::ObjectFile::createObjectFile( - entry->second.objectBuffer->getMemBufferRef()); - if (ObjOrErr) { - auto &Obj = *ObjOrErr.get(); + auto &Obj = *entry->second.objectFile; auto resOrErrJit = symbolizer.symbolizeCode(Obj, {jitSymbolizeAddr, llvm::object::SectionedAddress::UndefSection}); if (resOrErrJit) { auto &info = resOrErrJit.get(); if (info.FileName != "") { - if (found) { - std::string demangled = llvm::demangle(info.FunctionName); - if (mode == StackTraceMode::Debug || !isInfrastructureFrame(demangled, info.FileName)) { - ss << " at " << demangled << " [" << info.FileName << ":" << info.Line << "]\n"; - } + std::string demangled = llvm::demangle(info.FunctionName); + bool isInfra = isInfrastructureFrame(demangled, info.FileName); + if (!found && !isInfra) found = true; + if (found && (mode == StackTraceMode::Debug || !isInfra)) { + ss << " at " << demangled << " [" << info.FileName << ":" << info.Line << "]\n"; + addrHandled = true; + } else if (isInfra && !found) { + // Skip infra at the top + addrHandled = true; } - found = true; - continue; } } else { llvm::consumeError(resOrErrJit.takeError()); } - } else { - llvm::consumeError(ObjOrErr.takeError()); } } - if (found || (!found && !entry->second.name.empty())) { - if (found) { - std::string demangled = llvm::demangle(entry->second.name); - if (mode == StackTraceMode::Debug || !isInfrastructureFrame(demangled, "")) { - ss << " at " << demangled << " [JIT:0x" << std::hex << entry->first << std::dec << "]\n"; - } + if (!addrHandled && !entry->second.name.empty()) { + std::string demangled = llvm::demangle(entry->second.name); + bool isInfra = isInfrastructureFrame(demangled, ""); + if (!found && !isInfra) found = true; + if (found && (mode == StackTraceMode::Debug || !isInfra)) { + ss << " at " << demangled << " [JIT:0x" << std::hex << entry->first << std::dec << "]\n"; + addrHandled = true; + } else if (isInfra && !found) { + addrHandled = true; } - found = true; } - continue; + if (addrHandled) continue; } } } @@ -243,12 +254,9 @@ static void symbolizeStackChain(std::stringstream &ss, } if (!found) { - bool isSentinel = !dlSymbolName.empty() && - (dlSymbolName.find("throw") != std::string::npos || - dlSymbolName.find("Exception") != std::string::npos); - if (isSentinel || ++skipCount > 20) { + std::string demangled = llvm::demangle(dlSymbolName); + if (!isInfrastructureFrame(demangled, dlinfo.dli_fname ? dlinfo.dli_fname : "")) { found = true; - if (isSentinel) continue; } } @@ -289,28 +297,28 @@ static void symbolizeStackChain(std::stringstream &ss, auto resOrErr = symbolizer.symbolizeCode(currentModule, {symbolizeAddr, llvm::object::SectionedAddress::UndefSection}); if (resOrErr) { auto &info = resOrErr.get(); - if (found) { - std::string rawFn = (info.FunctionName != "") ? info.FunctionName : dlSymbolName; - std::string demangled = llvm::demangle(rawFn); - if (mode == StackTraceMode::Debug || !isInfrastructureFrame(demangled, info.FileName)) { - ss << " at " << demangled; - if (info.FileName != "") ss << " [" << info.FileName << ":" << info.Line << "]"; - else if (!currentModule.empty()) { - size_t lastSlash = currentModule.find_last_of('/'); - std::string base = (lastSlash != std::string::npos) ? currentModule.substr(lastSlash + 1) : currentModule; - ss << " [" << base << " + 0x" << std::hex << lookupAddr << std::dec << "]"; - } - ss << "\n"; + std::string rawFn = (info.FunctionName != "") ? info.FunctionName : dlSymbolName; + std::string demangled = llvm::demangle(rawFn); + bool isInfra = isInfrastructureFrame(demangled, info.FileName); + if (!found && !isInfra) found = true; + if (found && (mode == StackTraceMode::Debug || !isInfra)) { + ss << " at " << demangled; + if (info.FileName != "") ss << " [" << info.FileName << ":" << info.Line << "]"; + else if (!currentModule.empty()) { + size_t lastSlash = currentModule.find_last_of('/'); + std::string base = (lastSlash != std::string::npos) ? currentModule.substr(lastSlash + 1) : currentModule; + ss << " [" << base << " + 0x" << std::hex << lookupAddr << std::dec << "]"; } + ss << "\n"; } } else { llvm::consumeError(resOrErr.takeError()); - if (found) { - std::string demangled = llvm::demangle(dlSymbolName); - if (mode == StackTraceMode::Debug || !isInfrastructureFrame(demangled, currentModule)) { - if (!dlSymbolName.empty()) ss << " at " << demangled << " [" << currentModule << "]\n"; - else ss << " at 0x" << std::hex << addr << std::dec << " [" << currentModule << "]\n"; - } + std::string demangled = llvm::demangle(dlSymbolName); + bool isInfra = isInfrastructureFrame(demangled, currentModule); + if (!found && !isInfra) found = true; + if (found && (mode == StackTraceMode::Debug || !isInfra)) { + if (!dlSymbolName.empty()) ss << " at " << demangled << " [" << currentModule << "]\n"; + else ss << " at 0x" << std::hex << addr << std::dec << " [" << currentModule << "]\n"; } } } diff --git a/backend-v2/jit/JITEngine.cpp b/backend-v2/jit/JITEngine.cpp index 3b16c532..9884f49c 100644 --- a/backend-v2/jit/JITEngine.cpp +++ b/backend-v2/jit/JITEngine.cpp @@ -185,7 +185,7 @@ JITEngine::compileGeneric(std::function codegenFunc, auto Sym = jit->lookup(moduleName); if (Sym) { std::promise p; - p.set_value({*Sym, ""}); + p.set_value({*Sym, "", moduleName}); return p.get_future().share(); } } @@ -293,7 +293,7 @@ JITEngine::compileGeneric(std::function codegenFunc, activeCompilations.erase(moduleName); } - return JITResult{*Sym, std::move(ir)}; + return JITResult{*Sym, std::move(ir), fName}; } catch (...) { std::lock_guard lock(compilationMutex); activeCompilations.erase(moduleName); diff --git a/backend-v2/jit/JITEngine.h b/backend-v2/jit/JITEngine.h index b5eaefc7..523cbc9d 100644 --- a/backend-v2/jit/JITEngine.h +++ b/backend-v2/jit/JITEngine.h @@ -48,6 +48,7 @@ namespace rt { struct JITResult { llvm::orc::ExecutorAddr address; std::string optimizedIR; + std::string symbolName; }; class JITEngine { diff --git a/backend-v2/tests/CMakeLists.txt b/backend-v2/tests/CMakeLists.txt index 14352574..ff385249 100644 --- a/backend-v2/tests/CMakeLists.txt +++ b/backend-v2/tests/CMakeLists.txt @@ -23,6 +23,7 @@ set(TEST_MODULES codegen/DCE_test codegen/O3_DebugInfo_test codegen/AsyncStackTrace_test + codegen/DeepStackSymbolization_test VarUAF_repro_test JITSafety_test ) diff --git a/backend-v2/tests/codegen/DeepStackSymbolization_test.cpp b/backend-v2/tests/codegen/DeepStackSymbolization_test.cpp new file mode 100644 index 00000000..11baf530 --- /dev/null +++ b/backend-v2/tests/codegen/DeepStackSymbolization_test.cpp @@ -0,0 +1,222 @@ +#include "../../bridge/Exceptions.h" +#include "../../jit/JITEngine.h" +#include "../../runtime/RuntimeInterface.h" +#include "../../state/ThreadsafeCompilerState.h" +#include "../../tools/EdnParser.h" +#include "bytecode.pb.h" +#include +#include + +extern "C" { +#include "../../runtime/Class.h" +#include "../../runtime/Keyword.h" +#include "../../runtime/Var.h" +#include "../../runtime/tests/TestTools.h" +#include +} + +using namespace std; +using namespace rt; + +static void setup_test_metadata(rt::ThreadsafeCompilerState &compState, + rt::JITEngine &engine) { + clojure::rt::protobuf::bytecode::Programme astClasses; + std::string path = "tests/rt-classes.cljb"; + std::fstream classesInput(path, std::ios::in | std::ios::binary); + if (!classesInput.is_open()) { + path = "backend-v2/tests/rt-classes.cljb"; + classesInput.open(path, std::ios::in | std::ios::binary); + } + + if (classesInput.is_open()) { + if (!astClasses.ParseFromIstream(&classesInput)) { + fprintf(stderr, "Failed to parse bytecode from %s\n", path.c_str()); + return; + } + } else { + // If we can't find it, we'll try to proceed without it or fail gracefully + fprintf(stderr, "Could not open rt-classes.cljb\n"); + return; + } + + try { + auto res = engine + .compileAST(astClasses.nodes(0), "__classes", + llvm::OptimizationLevel::O0, false) + .get(); + RTValue classes = res.address.toPtr()(); + compState.storeInternalClasses(classes); + } catch (const LanguageException &e) { + fprintf(stderr, "LanguageException during setup_test_metadata: %s\n", + getExceptionString(e).c_str()); + throw; + } +} + +static rt::ThreadsafeCompilerState *gCompState = nullptr; +static rt::JITEngine *gEngine = nullptr; + +static MemoryState gInitialMemoryState; + +static int setup_test_group(void **state) { + initialise_memory(); + captureMemoryState(&gInitialMemoryState); + gCompState = new rt::ThreadsafeCompilerState(); + gEngine = new rt::JITEngine(*gCompState); + setup_test_metadata(*gCompState, *gEngine); + return 0; +} + +static int teardown_test_group(void **state) { + delete gEngine; + delete gCompState; + RuntimeInterface_cleanup(); + + MemoryState finalMemoryState; + captureMemoryState(&finalMemoryState); + bool leaked = false; + for (int i = 0; i < 256; i++) { + if (finalMemoryState.counts[i] != gInitialMemoryState.counts[i]) { + if (!leaked) { + printf("\n========== Memory Leak Detected ==========\n"); + printReferenceCounts(); + leaked = true; + } + printf("Type %d: expected %lu, got %lu\n", i + 1, + gInitialMemoryState.counts[i], finalMemoryState.counts[i]); + } + } + if (leaked) { + return -1; + } + + return 0; +} + +static void test_segfault_reproduction(void **state) { + (void)state; + + // 1. Register clojure.lang.Numbers for the "bottom" call + { + String *nameStr = String_create("clojure.lang.Numbers"); + Ptr_retain(nameStr); + Class *cls = Class_create(nameStr, nameStr, 0, nullptr); + ClassDescription *ext = new ClassDescription(); + ext->name = "clojure.lang.Numbers"; + + IntrinsicDescription addII; + addII.symbol = "Add"; + addII.type = CallType::Intrinsic; + addII.argTypes.push_back(ObjectTypeSet(integerType, false)); + addII.argTypes.push_back(ObjectTypeSet(integerType, false)); + addII.returnType = ObjectTypeSet(integerType, false); + ext->staticFns["add"].push_back(addII); + + IntrinsicDescription addGeneric; + addGeneric.symbol = "Numbers_add"; + addGeneric.type = CallType::Call; + addGeneric.argTypes.push_back(ObjectTypeSet::all()); + addGeneric.argTypes.push_back(ObjectTypeSet::all()); + addGeneric.returnType = ObjectTypeSet::dynamicType(); + ext->staticFns["add"].push_back(addGeneric); + + cls->compilerExtension = ext; + cls->compilerExtensionDestructor = delete_class_description; + gCompState->classRegistry.registerObject("clojure.lang.Numbers", cls); + } + + // 2. Register Repro class for our JIT chain + Class *reproCls = nullptr; + ClassDescription *reproExt = nullptr; + { + String *nameStr = String_create("Repro"); + Ptr_retain(nameStr); + reproCls = Class_create(nameStr, nameStr, 0, nullptr); + reproExt = new ClassDescription(); + reproExt->name = "Repro"; + reproCls->compilerExtension = reproExt; + reproCls->compilerExtensionDestructor = delete_class_description; + gCompState->classRegistry.registerObject("Repro", reproCls); + } + + const int NUM_FRAMES = 50; + + auto createJitFunction = [&](int depth) { + clojure::rt::protobuf::bytecode::Node node; + if (depth == NUM_FRAMES - 1) { + // (+ "hello" "world") -> throws IllegalArgumentException + node.set_op(clojure::rt::protobuf::bytecode::opStaticCall); + auto *sc = node.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("add"); + auto *arg1 = sc->add_args(); + arg1->set_op(clojure::rt::protobuf::bytecode::opConst); + auto *c1 = arg1->mutable_subnode()->mutable_const_(); + c1->set_type( + clojure::rt::protobuf::bytecode::ConstNode_ConstType_constTypeString); + c1->set_val("hello"); + auto *arg2 = sc->add_args(); + arg2->set_op(clojure::rt::protobuf::bytecode::opConst); + auto *c2 = arg2->mutable_subnode()->mutable_const_(); + c2->set_type( + clojure::rt::protobuf::bytecode::ConstNode_ConstType_constTypeString); + c2->set_val("world"); + } else { + // (Repro/f{depth+1}) + node.set_op(clojure::rt::protobuf::bytecode::opStaticCall); + auto *sc = node.mutable_subnode()->mutable_staticcall(); + sc->set_class_("Repro"); + sc->set_method("f" + to_string(depth + 1)); + } + + string modName = "repro_mod_" + to_string(depth); + try { + auto res = + gEngine->compileAST(node, modName, llvm::OptimizationLevel::O0, false) + .get(); + + // Register this function as a static method in Repro class so depth-1 can + // call it + IntrinsicDescription desc; + desc.symbol = res.symbolName; + desc.type = CallType::Call; + desc.returnType = ObjectTypeSet::dynamicType(); + reproExt->staticFns["f" + to_string(depth)].push_back(desc); + + return res.address; + } catch (const LanguageException &e) { + fprintf(stderr, "Compilation failed for depth %d: %s\n", depth, + getExceptionString(e).c_str()); + throw; + } + }; + + // Build in REVERSE order + llvm::orc::ExecutorAddr startAddr; + for (int i = NUM_FRAMES - 1; i >= 0; i--) { + auto addr = createJitFunction(i); + if (i == 0) + startAddr = addr; + } + + // Now call f0 + try { + cout << "Invoking JIT chain..." << endl; + auto f0 = startAddr.toPtr(); + f0(); + fail_msg("Expected exception"); + } catch (const LanguageException &e) { + cout << "Caught exception. Starting symbolization..." << endl; + string trace = getExceptionString(e, StackTraceMode::Debug); + cout << "Trace length: " << trace.length() << endl; + cout << "Trace summary:\n" << trace.substr(0, 1000) << "..." << endl; + assert_true(trace.length() > 0); + } +} + +int main() { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_segfault_reproduction), + }; + return cmocka_run_group_tests(tests, setup_test_group, teardown_test_group); +} From 27e2e8ed830e19eb1eef4a80942d9fe8155a8d54 Mon Sep 17 00:00:00 2001 From: Marek Lipert Date: Mon, 23 Mar 2026 18:23:24 +0100 Subject: [PATCH 04/10] Complete stack traces. --- backend-v2/bridge/Exceptions.cpp | 657 ++++++++++++++++-------------- backend-v2/bridge/Exceptions.h | 5 + backend-v2/jit/JITEngine.cpp | 39 +- backend-v2/runtime/CMakeLists.txt | 2 +- 4 files changed, 390 insertions(+), 313 deletions(-) diff --git a/backend-v2/bridge/Exceptions.cpp b/backend-v2/bridge/Exceptions.cpp index f76c5458..3bc3e7c0 100644 --- a/backend-v2/bridge/Exceptions.cpp +++ b/backend-v2/bridge/Exceptions.cpp @@ -1,225 +1,188 @@ #include "Exceptions.h" -#include "../runtime/Exceptions.h" -#include -#include -#include -#include -#include -#include -#include +#include "llvm/DebugInfo/Symbolize/Symbolize.h" +#include "llvm/Demangle/Demangle.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Support/Error.h" +#include +#include +#include +#include +#include +#include +#include #ifdef __APPLE__ -#include #include -#elif defined(__linux__) -#include -#include -#include #endif -#include -#include - namespace rt { thread_local std::shared_ptr gCurrentAsyncStack = nullptr; -struct JitInfo { +struct JitFunctionEntry { + uword_t size; std::string name; - size_t size; std::unique_ptr objectBuffer; mutable std::unique_ptr objectFile; }; +static std::map gJitFunctions; static std::mutex gJitMapMutex; -static std::map gJitFunctions; -extern "C" void registerJitFunction_C(uword_t addr, size_t size, - const char *name, const void *objData, - size_t objSize) { +void registerJitFunction(uword_t address, uword_t size, const char *name, + const void *objectData, size_t objectSize) { std::lock_guard lock(gJitMapMutex); - auto buffer = llvm::MemoryBuffer::getMemBufferCopy( - llvm::StringRef(static_cast(objData), objSize), - name ? name : "jit_object"); - gJitFunctions[addr] = {name ? name : "", size, std::move(buffer), nullptr}; -} - -std::shared_ptr captureCurrentStack() { - auto stack = std::make_shared(); - void *buffer[128]; - int size = backtrace(buffer, 128); - stack->addresses.reserve(size); - // Skip 2 frames to bypass captureCurrentStack and its direct caller (wrapper) - for (int i = 2; i < size; i++) { - stack->addresses.push_back(reinterpret_cast(buffer[i])); + JitFunctionEntry entry; + entry.size = size; + entry.name = name; + if (objectData && objectSize > 0) { + entry.objectBuffer = llvm::MemoryBuffer::getMemBufferCopy(llvm::StringRef( + reinterpret_cast(objectData), objectSize)); } - stack->parent = gCurrentAsyncStack; - return stack; + gJitFunctions[address] = std::move(entry); } -LanguageException::LanguageException(const std::string &name, RTValue message, - RTValue payload) { - this->payload = payload; - this->name = name; - this->message = message; - this->capturedStack = captureCurrentStack(); +extern "C" void registerJitFunction_C(uword_t address, uword_t size, + const char *name, const void *objectData, + size_t objectSize) { + registerJitFunction(address, size, name, objectData, objectSize); } -LanguageException::LanguageException(const LanguageException &other) { - this->name = other.name; - this->message = other.message; - this->payload = other.payload; - this->capturedStack = other.capturedStack; - retain(this->message); - retain(this->payload); -} - -LanguageException & -LanguageException::operator=(const LanguageException &other) { - if (this != &other) { - release(this->message); - release(this->payload); - this->name = other.name; - this->message = other.message; - this->payload = other.payload; - this->capturedStack = other.capturedStack; - retain(this->message); - retain(this->payload); - } - return *this; -} - -LanguageException::~LanguageException() noexcept { - release(message); - release(payload); -} - -void LanguageException::printRawTrace() const { - if (!capturedStack) return; - for (uword_t addr : capturedStack->addresses) { - printf(" [JIT ADDR] %p\n", (void *)addr); - } -} - -static bool isInfrastructureFrame(const std::string &fnName, - const std::string &fileName) { - if (fnName.empty()) - return false; - - // Patterns to skip in Friendly mode - static const char *skipPatterns[] = { +static bool isInfrastructureFrame(const std::string &name, + const std::string &file) { + static const std::vector skipPatterns = { "std::__1", "rt::InvokeManager", "rt::CodeGen", "rt::JITEngine", "rt::ThreadPool", - "rt::CleanupChain", - "rt::InstanceCallStub", - "InstanceCallSlowPath", + "rt::LanguageException", + "captureCurrentStack", + "throwArithmeticException_C", "throwInternalInconsistencyException", - "throwNoMatchingOverloadException", - "throwLanguageException", - "throwArityException", "throwIllegalArgumentException", - "throwIllegalStateException", - "throwUnsupportedOperationException", - "throwArithmeticException", - "throwIndexOutOfBoundsException", - "throwCodeGenerationException", - "asan_thread_start", - "__packaged_task", - "__invoke", - "__function", - "__alloc_func", - "__value_func", - "__packaged_task_func", - "__thread_proxy", - "__thread_execute", - "future", - "decltype", - "std::declval", + "throwNoMatchingOverloadException", + "InstanceCallSlowPath", + "asan_", + "__asan_", "cmocka_", - "_cmocka_"}; + "_cmocka_", + "libclang_rt.asan"}; - for (const char *p : skipPatterns) { - if (fnName.find(p) != std::string::npos) + for (const auto &pattern : skipPatterns) { + if (name.find(pattern) != std::string::npos) return true; } - // System headers / ASan libs / C++ machinery - if (fileName.find("/usr/include/c++/") != std::string::npos) - return true; - if (fileName.find("libclang_rt.asan") != std::string::npos) - return true; - if (fileName.find("__functional") != std::string::npos) - return true; - if (fileName.find("__type_traits") != std::string::npos) - return true; - if (fileName.find("/c++/v1/") != std::string::npos) + if (file.find("codegen/invoke/InvokeManager.") != std::string::npos || + file.find("codegen/CodeGen.") != std::string::npos || + file.find("jit/JITEngine.") != std::string::npos) { return true; + } return false; } -static void symbolizeStackChain(std::stringstream &ss, - const std::shared_ptr &stack, - llvm::symbolize::LLVMSymbolizer &symbolizer, - const std::string &moduleName, - const intptr_t slide, StackTraceMode mode) { - if (!stack) return; - +void symbolizeStackChain(std::stringstream &ss, + std::shared_ptr stack, + llvm::symbolize::LLVMSymbolizer &symbolizer, + const std::string &defaultModuleName, + const intptr_t defaultSlide, StackTraceMode mode) { auto current = stack; - bool isFirst = true; + bool isFirstStack = true; + std::string lastLine = ""; + while (current) { - bool found = (mode == StackTraceMode::Debug) || !isFirst; + bool foundFirstUserFrame = (mode == StackTraceMode::Debug) || !isFirstStack; for (uword_t addr : current->addresses) { - std::string currentModule = ""; - uword_t lookupAddr = addr; - uword_t symbolizeAddr = 0; - std::string dlSymbolName = ""; bool addrHandled = false; + std::vector currentAddrLines; - // 1. Check JIT map first + // 1. Check JIT map { std::lock_guard lock(gJitMapMutex); auto it = gJitFunctions.lower_bound(addr); - if (it != gJitFunctions.begin() || (it != gJitFunctions.end() && it->first == addr)) { - auto entry = (it != gJitFunctions.end() && it->first == addr) ? it : std::prev(it); - if (addr >= entry->first && addr < entry->first + entry->second.size) { - if (entry->second.objectBuffer) { - if (!entry->second.objectFile) { + if (it != gJitFunctions.begin() || + (it != gJitFunctions.end() && it->first == addr)) { + auto &entry = (it != gJitFunctions.end() && it->first == addr) + ? it->second + : std::prev(it)->second; + uword_t entryStart = (it != gJitFunctions.end() && it->first == addr) + ? it->first + : std::prev(it)->first; + + if (addr >= entryStart && addr < entryStart + entry.size) { + uword_t jitSymbolizeAddr = addr - entryStart; +#if defined(__aarch64__) || defined(__arm64__) + if (jitSymbolizeAddr >= 4) + jitSymbolizeAddr -= 4; +#else + if (jitSymbolizeAddr > 0) + jitSymbolizeAddr -= 1; +#endif + + bool jitInliningProcessed = false; + if (entry.objectBuffer) { + if (!entry.objectFile) { auto ObjOrErr = llvm::object::ObjectFile::createObjectFile( - entry->second.objectBuffer->getMemBufferRef()); - if (ObjOrErr) { - entry->second.objectFile = std::move(ObjOrErr.get()); - } else { + entry.objectBuffer->getMemBufferRef()); + if (ObjOrErr) + entry.objectFile = std::move(ObjOrErr.get()); + else llvm::consumeError(ObjOrErr.takeError()); - } } - if (entry->second.objectFile) { - uword_t jitSymbolizeAddr = addr - entry->first; -#if defined(__aarch64__) || defined(__arm64__) - if (jitSymbolizeAddr >= 4) jitSymbolizeAddr -= 4; -#else - if (jitSymbolizeAddr > 0) jitSymbolizeAddr -= 1; -#endif - auto &Obj = *entry->second.objectFile; - auto resOrErrJit = symbolizer.symbolizeCode(Obj, {jitSymbolizeAddr, llvm::object::SectionedAddress::UndefSection}); + if (entry.objectFile) { + auto resOrErrJit = symbolizer.symbolizeInlinedCode( + *entry.objectFile, + {jitSymbolizeAddr, + llvm::object::SectionedAddress::UndefSection}); if (resOrErrJit) { - auto &info = resOrErrJit.get(); - if (info.FileName != "") { - std::string demangled = llvm::demangle(info.FunctionName); - bool isInfra = isInfrastructureFrame(demangled, info.FileName); - if (!found && !isInfra) found = true; - if (found && (mode == StackTraceMode::Debug || !isInfra)) { - ss << " at " << demangled << " [" << info.FileName << ":" << info.Line << "]\n"; - addrHandled = true; - } else if (isInfra && !found) { - // Skip infra at the top - addrHandled = true; + auto &inlinedInfo = resOrErrJit.get(); + for (uint32_t i = 0; i < inlinedInfo.getNumberOfFrames(); + ++i) { + auto &info = inlinedInfo.getFrame(i); + std::string fnName = (info.FunctionName != "") + ? info.FunctionName + : entry.name; + std::string demangled = llvm::demangle(fnName); + bool isInfra = + isInfrastructureFrame(demangled, info.FileName); + + if (!foundFirstUserFrame && !isInfra) + foundFirstUserFrame = true; + + if (foundFirstUserFrame && !isInfra) { + std::stringstream frameSs; + frameSs << " at " << demangled; + if (info.FileName != "") + frameSs << " [" << info.FileName << ":" << info.Line + << "]"; + else + frameSs << " [JIT:0x" << std::hex << entryStart + << std::dec << "]"; + frameSs << "\n"; + currentAddrLines.push_back(frameSs.str()); + } else if (mode == StackTraceMode::Debug) { + std::stringstream frameSs; + frameSs << " at " << demangled; + if (info.FileName != "") + frameSs << " [" << info.FileName << ":" << info.Line + << "]"; + else + frameSs << " [JIT:0x" << std::hex << entryStart + << std::dec << "]"; + + frameSs << " [+0x" << std::hex << (addr - entryStart) + << std::dec << "]"; + if (i > 0) + frameSs << " (inlined)"; + frameSs << " @ 0x" << std::hex << addr << std::dec; + frameSs << "\n"; + currentAddrLines.push_back(frameSs.str()); } + jitInliningProcessed = true; } } else { llvm::consumeError(resOrErrJit.takeError()); @@ -227,164 +190,267 @@ static void symbolizeStackChain(std::stringstream &ss, } } - if (!addrHandled && !entry->second.name.empty()) { - std::string demangled = llvm::demangle(entry->second.name); + if (!jitInliningProcessed && !entry.name.empty()) { + std::string demangled = llvm::demangle(entry.name); bool isInfra = isInfrastructureFrame(demangled, ""); - if (!found && !isInfra) found = true; - if (found && (mode == StackTraceMode::Debug || !isInfra)) { - ss << " at " << demangled << " [JIT:0x" << std::hex << entry->first << std::dec << "]\n"; - addrHandled = true; - } else if (isInfra && !found) { - addrHandled = true; + if (!foundFirstUserFrame && !isInfra) + foundFirstUserFrame = true; + if (foundFirstUserFrame || mode == StackTraceMode::Debug) { + std::stringstream frameSs; + frameSs << " at " << demangled << " [JIT:0x" << std::hex + << entryStart << std::dec << "]"; + if (mode == StackTraceMode::Debug) { + frameSs << " [+0x" << std::hex << (addr - entryStart) + << std::dec << "] @ 0x" << std::hex << addr + << std::dec; + } + frameSs << "\n"; + currentAddrLines.push_back(frameSs.str()); } } - if (addrHandled) continue; + addrHandled = true; } } } - Dl_info dlinfo; - bool hasDlInfo = dladdr(reinterpret_cast(addr), &dlinfo); - if (hasDlInfo) { - if (dlinfo.dli_sname) dlSymbolName = dlinfo.dli_sname; - - // Skip thread entry points if we have a parent to stitch to - if (current->parent && (dlSymbolName == "thread_start" || dlSymbolName == "_pthread_start" || dlSymbolName == "start" || dlSymbolName == "asan_thread_start")) { - continue; - } - - if (!found) { - std::string demangled = llvm::demangle(dlSymbolName); - if (!isInfrastructureFrame(demangled, dlinfo.dli_fname ? dlinfo.dli_fname : "")) { - found = true; + // 2. Check non-JIT symbols + if (!addrHandled) { + Dl_info dlinfo; + bool hasDlInfo = dladdr(reinterpret_cast(addr), &dlinfo); + std::string currentModule = ""; + uword_t symbolizeAddr = 0; + std::string dlSymbolName = ""; + + if (hasDlInfo) { + if (dlinfo.dli_sname) + dlSymbolName = dlinfo.dli_sname; + + // Skip thread entry points if we have a parent to stitch to + if (current->parent && + (dlSymbolName == "thread_start" || + dlSymbolName == "_pthread_start" || dlSymbolName == "start" || + dlSymbolName == "asan_thread_start")) { + continue; } - } #ifdef __APPLE__ - if (dlinfo.dli_fbase == _dyld_get_image_header(0)) { - currentModule = moduleName; - lookupAddr = addr - slide; - } else if (dlinfo.dli_fname) { - currentModule = dlinfo.dli_fname; - lookupAddr = addr - reinterpret_cast(dlinfo.dli_fbase); - } -#else - if (dlinfo.dli_fname) { - currentModule = dlinfo.dli_fname; - lookupAddr = addr - reinterpret_cast(dlinfo.dli_fbase); - } -#endif - } - - if (!found) continue; - - symbolizeAddr = lookupAddr; -#if defined(__aarch64__) || defined(__arm64__) - if (symbolizeAddr >= 4) symbolizeAddr -= 4; + if (dlinfo.dli_fbase == _dyld_get_image_header(0)) { + currentModule = defaultModuleName; + symbolizeAddr = addr - defaultSlide; + } else if (dlinfo.dli_fname) { + currentModule = dlinfo.dli_fname; + symbolizeAddr = addr - reinterpret_cast(dlinfo.dli_fbase); + } #else - if (symbolizeAddr > 0) symbolizeAddr -= 1; + if (dlinfo.dli_fname) { + currentModule = dlinfo.dli_fname; + symbolizeAddr = addr - reinterpret_cast(dlinfo.dli_fbase); + } #endif + } - if (currentModule.empty()) { - currentModule = moduleName; -#if defined(__aarch64__) || defined(__arm64__) - symbolizeAddr = (addr - slide) - 4; -#else - symbolizeAddr = (addr - slide) - 1; -#endif - } + if (symbolizeAddr >= 4) + symbolizeAddr -= 4; + + auto resOrErr = symbolizer.symbolizeInlinedCode( + currentModule, + {symbolizeAddr, llvm::object::SectionedAddress::UndefSection}); + bool inliningSuccess = false; + if (resOrErr) { + auto &inlinedInfo = resOrErr.get(); + for (uint32_t i = 0; i < inlinedInfo.getNumberOfFrames(); ++i) { + auto &info = inlinedInfo.getFrame(i); + std::string rawFn = (info.FunctionName != "") + ? info.FunctionName + : dlSymbolName; + std::string demangled = llvm::demangle(rawFn); + bool isInfra = isInfrastructureFrame(demangled, info.FileName); + + if (!foundFirstUserFrame && !isInfra) + foundFirstUserFrame = true; + + if (foundFirstUserFrame && !isInfra) { + std::stringstream frameSs; + frameSs << " at " << demangled; + if (info.FileName != "") + frameSs << " [" << info.FileName << ":" << info.Line << "]"; + else if (!currentModule.empty()) { + size_t lastSlash = currentModule.find_last_of('/'); + std::string base = (lastSlash != std::string::npos) + ? currentModule.substr(lastSlash + 1) + : currentModule; + frameSs << " [" << base << "]"; + } + frameSs << "\n"; + currentAddrLines.push_back(frameSs.str()); + } else if (mode == StackTraceMode::Debug) { + std::stringstream frameSs; + frameSs << " at " << demangled; + if (info.FileName != "") + frameSs << " [" << info.FileName << ":" << info.Line << "]"; + else if (!currentModule.empty()) { + size_t lastSlash = currentModule.find_last_of('/'); + std::string base = (lastSlash != std::string::npos) + ? currentModule.substr(lastSlash + 1) + : currentModule; + frameSs << " [" << base << "]"; + } + if (i > 0) + frameSs << " (inlined)"; + frameSs << " @ 0x" << std::hex << addr << std::dec; + frameSs << "\n"; + currentAddrLines.push_back(frameSs.str()); + } + inliningSuccess = true; + } + } else { + llvm::consumeError(resOrErr.takeError()); + } - auto resOrErr = symbolizer.symbolizeCode(currentModule, {symbolizeAddr, llvm::object::SectionedAddress::UndefSection}); - if (resOrErr) { - auto &info = resOrErr.get(); - std::string rawFn = (info.FunctionName != "") ? info.FunctionName : dlSymbolName; - std::string demangled = llvm::demangle(rawFn); - bool isInfra = isInfrastructureFrame(demangled, info.FileName); - if (!found && !isInfra) found = true; - if (found && (mode == StackTraceMode::Debug || !isInfra)) { - ss << " at " << demangled; - if (info.FileName != "") ss << " [" << info.FileName << ":" << info.Line << "]"; - else if (!currentModule.empty()) { - size_t lastSlash = currentModule.find_last_of('/'); - std::string base = (lastSlash != std::string::npos) ? currentModule.substr(lastSlash + 1) : currentModule; - ss << " [" << base << " + 0x" << std::hex << lookupAddr << std::dec << "]"; + if (!inliningSuccess) { + std::string demangled = llvm::demangle(dlSymbolName); + bool isInfra = isInfrastructureFrame(demangled, currentModule); + if (!foundFirstUserFrame && !isInfra) + foundFirstUserFrame = true; + if (foundFirstUserFrame && !isInfra) { + std::stringstream frameSs; + if (!dlSymbolName.empty()) { + frameSs << " at " << demangled << " [" << currentModule << "]"; + } else { + frameSs << " at 0x" << std::hex << addr << std::dec << " [" + << currentModule << "]"; + } + frameSs << "\n"; + currentAddrLines.push_back(frameSs.str()); + } else if (mode == StackTraceMode::Debug) { + std::stringstream frameSs; + if (!dlSymbolName.empty()) { + frameSs << " at " << demangled << " [" << currentModule << "]"; + } else { + frameSs << " at 0x" << std::hex << addr << std::dec << " [" + << currentModule << "]"; + } + frameSs << " @ 0x" << std::hex << addr << std::dec; + frameSs << "\n"; + currentAddrLines.push_back(frameSs.str()); } - ss << "\n"; } - } else { - llvm::consumeError(resOrErr.takeError()); - std::string demangled = llvm::demangle(dlSymbolName); - bool isInfra = isInfrastructureFrame(demangled, currentModule); - if (!found && !isInfra) found = true; - if (found && (mode == StackTraceMode::Debug || !isInfra)) { - if (!dlSymbolName.empty()) ss << " at " << demangled << " [" << currentModule << "]\n"; - else ss << " at 0x" << std::hex << addr << std::dec << " [" << currentModule << "]\n"; + } + + for (const auto &line : currentAddrLines) { + if (line != lastLine) { + ss << line; + lastLine = line; } } } - current = current->parent; - isFirst = false; + isFirstStack = false; + } +} + +LanguageException::LanguageException(const std::string &name, RTValue message, + RTValue payload) + : name(name), message(message), payload(payload) { + capturedStack = captureCurrentStack(); + retain(message); + retain(payload); +} + +LanguageException::LanguageException(const LanguageException &other) + : name(other.name), message(other.message), payload(other.payload), + capturedStack(other.capturedStack) { + retain(message); + retain(payload); +} + +LanguageException & +LanguageException::operator=(const LanguageException &other) { + if (this != &other) { + release(message); + release(payload); + name = other.name; + message = other.message; + payload = other.payload; + capturedStack = other.capturedStack; + retain(message); + retain(payload); } + return *this; +} + +LanguageException::~LanguageException() noexcept { + release(message); + release(payload); } std::string LanguageException::toString(llvm::symbolize::LLVMSymbolizer &symbolizer, - const std::string &defaultModuleName, - const intptr_t defaultSlide, + const std::string &moduleName, const intptr_t slide, StackTraceMode mode) const { std::stringstream ss; - ss << "Exception: " << name << "\n"; - retain(message); - String *messageString = String_compactify(::toString(message)); - ss << "Message: " << String_c_str(messageString) << "\n"; - Ptr_release(messageString); - retain(payload); - String *payloadString = String_compactify(::toString(payload)); - ss << "Payload: " << String_c_str(payloadString) << "\n"; - Ptr_release(payloadString); + String *rawMsg = ::toString(message); + String *msgStr = String_compactify(rawMsg); + ss << "Message: " << String_c_str(msgStr) << "\n"; + if (msgStr != rawMsg) + Ptr_release(rawMsg); + Ptr_release(msgStr); - ss << "Stack Trace:\n"; - symbolizeStackChain(ss, capturedStack, symbolizer, defaultModuleName, defaultSlide, mode); + String *rawPay = ::toString(payload); + String *payStr = String_compactify(rawPay); + ss << "Payload: " << (payload == 0 ? "nil" : String_c_str(payStr)) << "\n"; + if (payStr != rawPay) + Ptr_release(rawPay); + Ptr_release(payStr); + ss << "Stack Trace:\n"; + symbolizeStackChain(ss, capturedStack, symbolizer, moduleName, slide, mode); return ss.str(); } -std::string getSelfExecutablePath() { -#ifdef __APPLE__ - char path[1024]; - uint32_t size = sizeof(path); - if (_NSGetExecutablePath(path, &size) == 0) - return std::string(path); -#elif defined(__linux__) - char path[PATH_MAX]; - ssize_t count = readlink("/proc/self/exe", path, PATH_MAX); - if (count != -1) { - return std::string(path, count); +std::shared_ptr captureCurrentStack() { + auto stack = std::make_shared(); + void *buffer[128]; + int size = backtrace(buffer, 128); + stack->addresses.reserve(size); + // Skip 2 frames + for (int i = 2; i < size; i++) { + stack->addresses.push_back(reinterpret_cast(buffer[i])); } -#endif - return ""; + stack->parent = gCurrentAsyncStack; + return stack; } -std::string getExceptionString(const LanguageException &e, StackTraceMode mode) { - llvm::symbolize::LLVMSymbolizer::Options options; - options.Demangle = true; - options.PrintFunctions = llvm::symbolize::FunctionNameKind::LinkageName; - llvm::symbolize::LLVMSymbolizer symbolizer(options); +void printReferenceCounts() { + for (int i = 0; i < 256; i++) { + uword_t count = + atomic_load_explicit(&objectCount[i], std::memory_order_relaxed); + if (count > 0) { + printf("Type %d: %lu\n", i + 1, (unsigned long)count); + } + } +} - std::string exePath = getSelfExecutablePath(); - std::string moduleName = exePath; +std::string getExceptionString(const LanguageException &e, + StackTraceMode mode) { + static llvm::symbolize::LLVMSymbolizer symbolizer; #ifdef __APPLE__ + char path[1024]; + uint32_t size = sizeof(path); + _NSGetExecutablePath(path, &size); + std::string exePath = path; size_t lastSlash = exePath.find_last_of('/'); std::string basename = (lastSlash != std::string::npos) ? exePath.substr(lastSlash + 1) : "clojure-rt"; - moduleName = exePath + ".dSYM/Contents/Resources/DWARF/" + basename; + std::string moduleName = + exePath + ".dSYM/Contents/Resources/DWARF/" + basename; intptr_t slide = _dyld_get_image_vmaddr_slide(0); #else + std::string moduleName = "/proc/self/exe"; intptr_t slide = 0; #endif @@ -396,23 +462,24 @@ std::string getExceptionString(const LanguageException &e, StackTraceMode mode) void throwInternalInconsistencyException(const std::string &errorMessage) { throw rt::LanguageException( "InternalInconsistencyException", - RT_boxPtr(String_createDynamicStr(errorMessage.c_str())), RT_boxNil()); + RT_boxPtr(::String_createDynamicStr(errorMessage.c_str())), RT_boxNil()); } extern "C" void throwInternalInconsistencyException_C(const char *errorMessage) { - throw rt::LanguageException("InternalInconsistencyException", - RT_boxPtr(String_createDynamicStr(errorMessage)), - RT_boxNil()); + throw rt::LanguageException( + "InternalInconsistencyException", + RT_boxPtr(::String_createDynamicStr(errorMessage)), RT_boxNil()); } -extern "C" void throwNoMatchingOverloadException_C(const char *className, - const char *methodName) { +extern "C" [[noreturn]] void +throwNoMatchingOverloadException_C(const char *className, + const char *methodName) { std::stringstream ss; - ss << "No matching overload found for " << className << "/" << methodName; + ss << "No matching overload found for " << className << "::" << methodName; throw rt::LanguageException( - "NoMatchingOverloadException", - RT_boxPtr(String_createDynamicStr(ss.str().c_str())), RT_boxNil()); + "InternalInconsistencyException", + RT_boxPtr(::String_createDynamicStr(ss.str().c_str())), RT_boxNil()); } extern "C" void throwLanguageException_C(const char *name, RTValue message, @@ -424,31 +491,31 @@ extern "C" void throwArityException_C(int expected, int actual) { std::stringstream ss; ss << "Wrong number of args (" << actual << ") passed to function"; throw rt::LanguageException( - "ArityException", RT_boxPtr(String_createDynamicStr(ss.str().c_str())), + "ArityException", RT_boxPtr(::String_createDynamicStr(ss.str().c_str())), RT_boxNil()); } extern "C" void throwIllegalArgumentException_C(const char *message) { throw rt::LanguageException("IllegalArgumentException", - RT_boxPtr(String_createDynamicStr(message)), + RT_boxPtr(::String_createDynamicStr(message)), RT_boxNil()); } extern "C" void throwIllegalStateException_C(const char *message) { throw rt::LanguageException("IllegalStateException", - RT_boxPtr(String_createDynamicStr(message)), + RT_boxPtr(::String_createDynamicStr(message)), RT_boxNil()); } extern "C" void throwUnsupportedOperationException_C(const char *message) { throw rt::LanguageException("UnsupportedOperationException", - RT_boxPtr(String_createDynamicStr(message)), + RT_boxPtr(::String_createDynamicStr(message)), RT_boxNil()); } extern "C" void throwArithmeticException_C(const char *message) { throw rt::LanguageException("ArithmeticException", - RT_boxPtr(String_createDynamicStr(message)), + RT_boxPtr(::String_createDynamicStr(message)), RT_boxNil()); } @@ -457,7 +524,7 @@ extern "C" void throwIndexOutOfBoundsException_C(uword_t index, uword_t count) { ss << "Index out of bounds: " << index << " (count: " << count << ")"; throw rt::LanguageException( "IndexOutOfBoundsException", - RT_boxPtr(String_createDynamicStr(ss.str().c_str())), RT_boxNil()); + RT_boxPtr(::String_createDynamicStr(ss.str().c_str())), RT_boxNil()); } void throwCodeGenerationException(const std::string &errorMessage, @@ -470,5 +537,5 @@ void throwCodeGenerationException(const std::string &errorMessage, retval << std::string(node.form().length(), '^') << "\n"; throw rt::LanguageException( "CodeGenerationException", - RT_boxPtr(String_createDynamicStr(retval.str().c_str())), RT_boxNil()); + RT_boxPtr(::String_createDynamicStr(retval.str().c_str())), RT_boxNil()); } diff --git a/backend-v2/bridge/Exceptions.h b/backend-v2/bridge/Exceptions.h index 12a37265..cab4e054 100644 --- a/backend-v2/bridge/Exceptions.h +++ b/backend-v2/bridge/Exceptions.h @@ -47,6 +47,11 @@ class LanguageException : public std::exception { const std::string &moduleName = "JITMemoryBuffer", const intptr_t slide = 0x0, StackTraceMode mode = StackTraceMode::Friendly) const; + + const std::string &getName() const { return name; } + RTValue getMessage() const { return message; } + RTValue getPayload() const { return payload; } + std::shared_ptr getCapturedStack() const { return capturedStack; } }; std::string getExceptionString(const LanguageException &e, diff --git a/backend-v2/jit/JITEngine.cpp b/backend-v2/jit/JITEngine.cpp index 9884f49c..94651202 100644 --- a/backend-v2/jit/JITEngine.cpp +++ b/backend-v2/jit/JITEngine.cpp @@ -2,7 +2,6 @@ #include "../RuntimeHeaders.h" #include "../runtime/JITSafety.h" - #include "../runtime/Numbers.h" #include "../tools/EdnParser.h" #include "bridge/Exceptions.h" @@ -54,14 +53,15 @@ void JITEngine::unregisterThread() { std::atomic JITEngine::instanceExists{false}; - -JITEngine::CapturedObject JITEngine::captureObject(const llvm::MemoryBuffer &Obj) { +JITEngine::CapturedObject +JITEngine::captureObject(const llvm::MemoryBuffer &Obj) { std::string id = Obj.getBufferIdentifier().str(); CapturedObject captured; captured.buffer = llvm::MemoryBuffer::getMemBufferCopy(Obj.getBuffer(), id); // Pre-parse symbols to make lookup in compileGeneric O(1) per buffer - auto ObjOrErr = llvm::object::ObjectFile::createObjectFile(captured.buffer->getMemBufferRef()); + auto ObjOrErr = llvm::object::ObjectFile::createObjectFile( + captured.buffer->getMemBufferRef()); if (ObjOrErr) { auto &ParsedObj = *ObjOrErr.get(); for (auto const &SymEntry : ParsedObj.symbols()) { @@ -261,11 +261,12 @@ JITEngine::compileGeneric(std::function codegenFunc, { std::lock_guard lock(this->engineMutex); - + // Find the object buffer that contains our symbol. // This is much more robust than name-based matching. size_t foundIdx = std::string::npos; - for (size_t i = 0; i < this->capturedObjectBuffers.size(); ++i) { + for (size_t i = 0; i < this->capturedObjectBuffers.size(); + ++i) { if (this->capturedObjectBuffers[i].symbols.count(fName)) { foundIdx = i; break; @@ -274,17 +275,22 @@ JITEngine::compileGeneric(std::function codegenFunc, if (foundIdx != std::string::npos) { auto &captured = this->capturedObjectBuffers[foundIdx]; - // printf("JIT: Found buffer for %s (size %zu)\n", fName.c_str(), captured.buffer->getBufferSize()); - registerJitFunction_C(Sym->getValue(), captured.buffer->getBufferSize(), fName.c_str(), - captured.buffer->getBufferStart(), - captured.buffer->getBufferSize()); - this->capturedObjectBuffers.erase(this->capturedObjectBuffers.begin() + foundIdx); + // printf("JIT: Found buffer for %s (size %zu)\n", + // fName.c_str(), captured.buffer->getBufferSize()); + registerJitFunction_C( + Sym->getValue(), captured.buffer->getBufferSize(), + fName.c_str(), captured.buffer->getBufferStart(), + captured.buffer->getBufferSize()); + this->capturedObjectBuffers.erase( + this->capturedObjectBuffers.begin() + foundIdx); } else { - // printf("JIT: FAILED to find buffer for %s among %zu candidates\n", fName.c_str(), this->capturedObjectBuffers.size()); - // Fallback for cases where symbol might not be in the dynamic symbol table - // of the object (though for JIT it usually is). - registerJitFunction_C(Sym->getValue(), 1024*1024, fName.c_str(), - nullptr, 0); + // printf("JIT: FAILED to find buffer for %s among %zu + // candidates\n", fName.c_str(), + // this->capturedObjectBuffers.size()); Fallback for cases + // where symbol might not be in the dynamic symbol table of + // the object (though for JIT it usually is). + registerJitFunction_C(Sym->getValue(), 1024 * 1024, + fName.c_str(), nullptr, 0); } } @@ -607,7 +613,6 @@ void JITEngine::optimize(llvm::Module &M, llvm::OptimizationLevel Level, } if (Level != llvm::OptimizationLevel::O0 && runtimeBitcodeBuffer) { - auto runtimeModuleOrErr = llvm::parseBitcodeFile(*runtimeBitcodeBuffer, M.getContext()); if (!runtimeModuleOrErr) { diff --git a/backend-v2/runtime/CMakeLists.txt b/backend-v2/runtime/CMakeLists.txt index 959aa5f4..794cc8fe 100644 --- a/backend-v2/runtime/CMakeLists.txt +++ b/backend-v2/runtime/CMakeLists.txt @@ -94,7 +94,7 @@ foreach(src ${SOURCES}) COMMAND ${CLANG_EXECUTABLE} -emit-llvm -c ${CMAKE_CURRENT_SOURCE_DIR}/${src} -iquote ${CMAKE_CURRENT_SOURCE_DIR} -I${GMP_INCLUDE_DIRS} - -O3 -fPIC -fexceptions -funwind-tables ${EMULATED_TLS_FLAG} + -O3 -gline-tables-only -fPIC -fexceptions -funwind-tables ${EMULATED_TLS_FLAG} -o ${bc_output} DEPENDS ${src} COMMENT "Generating LLVM Bitcode for ${src}" From 61235fbe8305bebb9cf6ce0f7f4e83f6b6edbbec Mon Sep 17 00:00:00 2001 From: Marek Lipert Date: Mon, 23 Mar 2026 19:20:44 +0100 Subject: [PATCH 05/10] Refactor --- backend-v2/bridge/Exceptions.cpp | 304 ++++++++++--------------------- backend-v2/codegen/CodeGen.h | 2 +- 2 files changed, 97 insertions(+), 209 deletions(-) diff --git a/backend-v2/bridge/Exceptions.cpp b/backend-v2/bridge/Exceptions.cpp index 3bc3e7c0..d5b0d94c 100644 --- a/backend-v2/bridge/Exceptions.cpp +++ b/backend-v2/bridge/Exceptions.cpp @@ -22,8 +22,6 @@ thread_local std::shared_ptr gCurrentAsyncStack = nullptr; struct JitFunctionEntry { uword_t size; std::string name; - std::unique_ptr objectBuffer; - mutable std::unique_ptr objectFile; }; static std::map gJitFunctions; @@ -34,11 +32,7 @@ void registerJitFunction(uword_t address, uword_t size, const char *name, std::lock_guard lock(gJitMapMutex); JitFunctionEntry entry; entry.size = size; - entry.name = name; - if (objectData && objectSize > 0) { - entry.objectBuffer = llvm::MemoryBuffer::getMemBufferCopy(llvm::StringRef( - reinterpret_cast(objectData), objectSize)); - } + entry.name = name ? name : "unknown"; gJitFunctions[address] = std::move(entry); } @@ -96,117 +90,39 @@ void symbolizeStackChain(std::stringstream &ss, bool foundFirstUserFrame = (mode == StackTraceMode::Debug) || !isFirstStack; for (uword_t addr : current->addresses) { + if (addr == 0) + continue; bool addrHandled = false; std::vector currentAddrLines; - // 1. Check JIT map + // 1. Check JIT functions { std::lock_guard lock(gJitMapMutex); auto it = gJitFunctions.lower_bound(addr); - if (it != gJitFunctions.begin() || - (it != gJitFunctions.end() && it->first == addr)) { - auto &entry = (it != gJitFunctions.end() && it->first == addr) - ? it->second - : std::prev(it)->second; - uword_t entryStart = (it != gJitFunctions.end() && it->first == addr) - ? it->first - : std::prev(it)->first; + if (it != gJitFunctions.begin() && + (it == gJitFunctions.end() || it->first > addr)) { + --it; + } + if (it != gJitFunctions.end()) { + uword_t entryStart = it->first; + const JitFunctionEntry &entry = it->second; if (addr >= entryStart && addr < entryStart + entry.size) { - uword_t jitSymbolizeAddr = addr - entryStart; -#if defined(__aarch64__) || defined(__arm64__) - if (jitSymbolizeAddr >= 4) - jitSymbolizeAddr -= 4; -#else - if (jitSymbolizeAddr > 0) - jitSymbolizeAddr -= 1; -#endif - - bool jitInliningProcessed = false; - if (entry.objectBuffer) { - if (!entry.objectFile) { - auto ObjOrErr = llvm::object::ObjectFile::createObjectFile( - entry.objectBuffer->getMemBufferRef()); - if (ObjOrErr) - entry.objectFile = std::move(ObjOrErr.get()); - else - llvm::consumeError(ObjOrErr.takeError()); - } - - if (entry.objectFile) { - auto resOrErrJit = symbolizer.symbolizeInlinedCode( - *entry.objectFile, - {jitSymbolizeAddr, - llvm::object::SectionedAddress::UndefSection}); - if (resOrErrJit) { - auto &inlinedInfo = resOrErrJit.get(); - for (uint32_t i = 0; i < inlinedInfo.getNumberOfFrames(); - ++i) { - auto &info = inlinedInfo.getFrame(i); - std::string fnName = (info.FunctionName != "") - ? info.FunctionName - : entry.name; - std::string demangled = llvm::demangle(fnName); - bool isInfra = - isInfrastructureFrame(demangled, info.FileName); - - if (!foundFirstUserFrame && !isInfra) - foundFirstUserFrame = true; - - if (foundFirstUserFrame && !isInfra) { - std::stringstream frameSs; - frameSs << " at " << demangled; - if (info.FileName != "") - frameSs << " [" << info.FileName << ":" << info.Line - << "]"; - else - frameSs << " [JIT:0x" << std::hex << entryStart - << std::dec << "]"; - frameSs << "\n"; - currentAddrLines.push_back(frameSs.str()); - } else if (mode == StackTraceMode::Debug) { - std::stringstream frameSs; - frameSs << " at " << demangled; - if (info.FileName != "") - frameSs << " [" << info.FileName << ":" << info.Line - << "]"; - else - frameSs << " [JIT:0x" << std::hex << entryStart - << std::dec << "]"; - - frameSs << " [+0x" << std::hex << (addr - entryStart) - << std::dec << "]"; - if (i > 0) - frameSs << " (inlined)"; - frameSs << " @ 0x" << std::hex << addr << std::dec; - frameSs << "\n"; - currentAddrLines.push_back(frameSs.str()); - } - jitInliningProcessed = true; - } - } else { - llvm::consumeError(resOrErrJit.takeError()); - } - } - } + std::string demangled = llvm::demangle(entry.name); + bool isInfra = isInfrastructureFrame(demangled, ""); + if (!foundFirstUserFrame && !isInfra) + foundFirstUserFrame = true; - if (!jitInliningProcessed && !entry.name.empty()) { - std::string demangled = llvm::demangle(entry.name); - bool isInfra = isInfrastructureFrame(demangled, ""); - if (!foundFirstUserFrame && !isInfra) - foundFirstUserFrame = true; - if (foundFirstUserFrame || mode == StackTraceMode::Debug) { - std::stringstream frameSs; - frameSs << " at " << demangled << " [JIT:0x" << std::hex - << entryStart << std::dec << "]"; - if (mode == StackTraceMode::Debug) { - frameSs << " [+0x" << std::hex << (addr - entryStart) - << std::dec << "] @ 0x" << std::hex << addr - << std::dec; - } - frameSs << "\n"; - currentAddrLines.push_back(frameSs.str()); + if (foundFirstUserFrame || mode == StackTraceMode::Debug) { + std::stringstream frameSs; + frameSs << " at " << demangled << " [JIT:0x" << std::hex + << entryStart << std::dec << "]"; + if (mode == StackTraceMode::Debug) { + frameSs << " [+0x" << std::hex << (addr - entryStart) + << std::dec << "] @ 0x" << std::hex << addr << std::dec; } + frameSs << "\n"; + currentAddrLines.push_back(frameSs.str()); } addrHandled = true; } @@ -216,14 +132,9 @@ void symbolizeStackChain(std::stringstream &ss, // 2. Check non-JIT symbols if (!addrHandled) { Dl_info dlinfo; - bool hasDlInfo = dladdr(reinterpret_cast(addr), &dlinfo); - std::string currentModule = ""; - uword_t symbolizeAddr = 0; - std::string dlSymbolName = ""; - - if (hasDlInfo) { - if (dlinfo.dli_sname) - dlSymbolName = dlinfo.dli_sname; + if (dladdr(reinterpret_cast(addr), &dlinfo)) { + std::string dlSymbolName = dlinfo.dli_sname ? dlinfo.dli_sname : ""; + std::string demangledDl = llvm::demangle(dlSymbolName); // Skip thread entry points if we have a parent to stitch to if (current->parent && @@ -233,6 +144,9 @@ void symbolizeStackChain(std::stringstream &ss, continue; } + std::string currentModule = ""; + uword_t symbolizeAddr = 0; + #ifdef __APPLE__ if (dlinfo.dli_fbase == _dyld_get_image_header(0)) { currentModule = defaultModuleName; @@ -247,92 +161,75 @@ void symbolizeStackChain(std::stringstream &ss, symbolizeAddr = addr - reinterpret_cast(dlinfo.dli_fbase); } #endif - } - if (symbolizeAddr >= 4) - symbolizeAddr -= 4; - - auto resOrErr = symbolizer.symbolizeInlinedCode( - currentModule, - {symbolizeAddr, llvm::object::SectionedAddress::UndefSection}); - bool inliningSuccess = false; - if (resOrErr) { - auto &inlinedInfo = resOrErr.get(); - for (uint32_t i = 0; i < inlinedInfo.getNumberOfFrames(); ++i) { - auto &info = inlinedInfo.getFrame(i); - std::string rawFn = (info.FunctionName != "") - ? info.FunctionName - : dlSymbolName; - std::string demangled = llvm::demangle(rawFn); - bool isInfra = isInfrastructureFrame(demangled, info.FileName); + if (!currentModule.empty()) { + if (symbolizeAddr >= 4) + symbolizeAddr -= 4; + + auto resOrErr = symbolizer.symbolizeInlinedCode( + currentModule, + {symbolizeAddr, llvm::object::SectionedAddress::UndefSection}); + + if (resOrErr) { + auto &inlinedInfo = resOrErr.get(); + for (uint32_t i = 0; i < inlinedInfo.getNumberOfFrames(); ++i) { + auto &info = inlinedInfo.getFrame(i); + std::string rawFn = (info.FunctionName != "") + ? info.FunctionName + : dlSymbolName; + std::string demangled = llvm::demangle(rawFn); + bool isInfra = isInfrastructureFrame(demangled, info.FileName); + + if (!foundFirstUserFrame && !isInfra) + foundFirstUserFrame = true; + + if (foundFirstUserFrame && !isInfra) { + std::stringstream frameSs; + frameSs << " at " << demangled; + if (info.FileName != "") + frameSs << " [" << info.FileName << ":" << info.Line << "]"; + else + frameSs << " [" << currentModule << "]"; + frameSs << "\n"; + currentAddrLines.push_back(frameSs.str()); + } else if (mode == StackTraceMode::Debug) { + std::stringstream frameSs; + frameSs << " at " << demangled; + if (info.FileName != "") + frameSs << " [" << info.FileName << ":" << info.Line << "]"; + else + frameSs << " [" << currentModule << "]"; + if (i > 0) + frameSs << " (inlined)"; + frameSs << " @ 0x" << std::hex << addr << std::dec; + frameSs << "\n"; + currentAddrLines.push_back(frameSs.str()); + } + } + } else { + llvm::consumeError(resOrErr.takeError()); + } + } + if (currentAddrLines.empty()) { + std::string demangled = llvm::demangle(dlSymbolName); + bool isInfra = isInfrastructureFrame(demangled, ""); if (!foundFirstUserFrame && !isInfra) foundFirstUserFrame = true; - - if (foundFirstUserFrame && !isInfra) { + if (foundFirstUserFrame || mode == StackTraceMode::Debug) { std::stringstream frameSs; - frameSs << " at " << demangled; - if (info.FileName != "") - frameSs << " [" << info.FileName << ":" << info.Line << "]"; - else if (!currentModule.empty()) { - size_t lastSlash = currentModule.find_last_of('/'); - std::string base = (lastSlash != std::string::npos) - ? currentModule.substr(lastSlash + 1) - : currentModule; - frameSs << " [" << base << "]"; + if (!dlSymbolName.empty()) { + frameSs << " at " << demangled << " [" + << (dlinfo.dli_fname ? dlinfo.dli_fname : "unknown") + << "]"; + } else { + frameSs << " at 0x" << std::hex << addr << std::dec << " [" + << (dlinfo.dli_fname ? dlinfo.dli_fname : "unknown") + << "]"; } frameSs << "\n"; currentAddrLines.push_back(frameSs.str()); - } else if (mode == StackTraceMode::Debug) { - std::stringstream frameSs; - frameSs << " at " << demangled; - if (info.FileName != "") - frameSs << " [" << info.FileName << ":" << info.Line << "]"; - else if (!currentModule.empty()) { - size_t lastSlash = currentModule.find_last_of('/'); - std::string base = (lastSlash != std::string::npos) - ? currentModule.substr(lastSlash + 1) - : currentModule; - frameSs << " [" << base << "]"; - } - if (i > 0) - frameSs << " (inlined)"; - frameSs << " @ 0x" << std::hex << addr << std::dec; - frameSs << "\n"; - currentAddrLines.push_back(frameSs.str()); - } - inliningSuccess = true; - } - } else { - llvm::consumeError(resOrErr.takeError()); - } - - if (!inliningSuccess) { - std::string demangled = llvm::demangle(dlSymbolName); - bool isInfra = isInfrastructureFrame(demangled, currentModule); - if (!foundFirstUserFrame && !isInfra) - foundFirstUserFrame = true; - if (foundFirstUserFrame && !isInfra) { - std::stringstream frameSs; - if (!dlSymbolName.empty()) { - frameSs << " at " << demangled << " [" << currentModule << "]"; - } else { - frameSs << " at 0x" << std::hex << addr << std::dec << " [" - << currentModule << "]"; - } - frameSs << "\n"; - currentAddrLines.push_back(frameSs.str()); - } else if (mode == StackTraceMode::Debug) { - std::stringstream frameSs; - if (!dlSymbolName.empty()) { - frameSs << " at " << demangled << " [" << currentModule << "]"; - } else { - frameSs << " at 0x" << std::hex << addr << std::dec << " [" - << currentModule << "]"; } - frameSs << " @ 0x" << std::hex << addr << std::dec; - frameSs << "\n"; - currentAddrLines.push_back(frameSs.str()); } } } @@ -353,16 +250,11 @@ LanguageException::LanguageException(const std::string &name, RTValue message, RTValue payload) : name(name), message(message), payload(payload) { capturedStack = captureCurrentStack(); - retain(message); - retain(payload); } LanguageException::LanguageException(const LanguageException &other) : name(other.name), message(other.message), payload(other.payload), - capturedStack(other.capturedStack) { - retain(message); - retain(payload); -} + capturedStack(other.capturedStack) {} LanguageException & LanguageException::operator=(const LanguageException &other) { @@ -391,18 +283,14 @@ LanguageException::toString(llvm::symbolize::LLVMSymbolizer &symbolizer, std::stringstream ss; ss << "Exception: " << name << "\n"; - String *rawMsg = ::toString(message); - String *msgStr = String_compactify(rawMsg); + retain(message); + String *msgStr = String_compactify(::toString(message)); ss << "Message: " << String_c_str(msgStr) << "\n"; - if (msgStr != rawMsg) - Ptr_release(rawMsg); Ptr_release(msgStr); - String *rawPay = ::toString(payload); - String *payStr = String_compactify(rawPay); + retain(payload); + String *payStr = String_compactify(::toString(payload)); ss << "Payload: " << (payload == 0 ? "nil" : String_c_str(payStr)) << "\n"; - if (payStr != rawPay) - Ptr_release(rawPay); Ptr_release(payStr); ss << "Stack Trace:\n"; @@ -478,7 +366,7 @@ throwNoMatchingOverloadException_C(const char *className, std::stringstream ss; ss << "No matching overload found for " << className << "::" << methodName; throw rt::LanguageException( - "InternalInconsistencyException", + "NoMatchingOverloadException", RT_boxPtr(::String_createDynamicStr(ss.str().c_str())), RT_boxNil()); } diff --git a/backend-v2/codegen/CodeGen.h b/backend-v2/codegen/CodeGen.h index 1f9862a1..6d732a68 100644 --- a/backend-v2/codegen/CodeGen.h +++ b/backend-v2/codegen/CodeGen.h @@ -79,7 +79,7 @@ class CodeGen { TheModule->addModuleFlag(llvm::Module::Warning, "Debug Info Version", llvm::DEBUG_METADATA_VERSION); #ifdef __APPLE__ - TheModule->addModuleFlag(llvm::Module::Warning, "Dwarf Version", 4); + TheModule->addModuleFlag(llvm::Module::Warning, "Dwarf Version", 5); #endif DIB = std::make_unique(*TheModule); CU = DIB->createCompileUnit(llvm::dwarf::DW_LANG_C, From 648e539d636b46e30e37179aaf2c68b35ff00f65 Mon Sep 17 00:00:00 2001 From: Marek Lipert Date: Mon, 23 Mar 2026 19:39:29 +0100 Subject: [PATCH 06/10] Complete exceptions --- backend-v2/bridge/Exceptions.cpp | 90 ++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 10 deletions(-) diff --git a/backend-v2/bridge/Exceptions.cpp b/backend-v2/bridge/Exceptions.cpp index d5b0d94c..20d19298 100644 --- a/backend-v2/bridge/Exceptions.cpp +++ b/backend-v2/bridge/Exceptions.cpp @@ -3,6 +3,7 @@ #include "llvm/Demangle/Demangle.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Support/Error.h" +#include "llvm/Support/MemoryBuffer.h" #include #include #include @@ -22,10 +23,12 @@ thread_local std::shared_ptr gCurrentAsyncStack = nullptr; struct JitFunctionEntry { uword_t size; std::string name; + std::vector objectData; }; static std::map gJitFunctions; static std::mutex gJitMapMutex; +static std::mutex gSymbolizerMutex; void registerJitFunction(uword_t address, uword_t size, const char *name, const void *objectData, size_t objectSize) { @@ -33,6 +36,10 @@ void registerJitFunction(uword_t address, uword_t size, const char *name, JitFunctionEntry entry; entry.size = size; entry.name = name ? name : "unknown"; + if (objectData && objectSize > 0) { + entry.objectData.assign(static_cast(objectData), + static_cast(objectData) + objectSize); + } gJitFunctions[address] = std::move(entry); } @@ -110,19 +117,78 @@ void symbolizeStackChain(std::stringstream &ss, if (addr >= entryStart && addr < entryStart + entry.size) { std::string demangled = llvm::demangle(entry.name); bool isInfra = isInfrastructureFrame(demangled, ""); + if (!foundFirstUserFrame && !isInfra) foundFirstUserFrame = true; if (foundFirstUserFrame || mode == StackTraceMode::Debug) { - std::stringstream frameSs; - frameSs << " at " << demangled << " [JIT:0x" << std::hex - << entryStart << std::dec << "]"; - if (mode == StackTraceMode::Debug) { - frameSs << " [+0x" << std::hex << (addr - entryStart) - << std::dec << "] @ 0x" << std::hex << addr << std::dec; + if (mode == StackTraceMode::Debug || !isInfra) { + // Try to get detailed info from objectData if present + bool detailedInfoFound = false; + if (!entry.objectData.empty()) { + auto buffer = llvm::MemoryBuffer::getMemBuffer( + llvm::StringRef(reinterpret_cast(entry.objectData.data()), + entry.objectData.size()), + entry.name, false); + auto objOrErr = llvm::object::ObjectFile::createObjectFile(buffer->getMemBufferRef()); + if (objOrErr) { + uword_t offset = addr - entryStart; + if (offset >= 4) offset -= 4; + + // Use a LOCAL symbolizer for each JIT frame to be absolutely sure about lifetime and thread safety + llvm::symbolize::LLVMSymbolizer localSymbolizer; + auto resOrErr = localSymbolizer.symbolizeInlinedCode( + *objOrErr.get(), + {offset, llvm::object::SectionedAddress::UndefSection}); + + if (resOrErr) { + auto &inlinedInfo = resOrErr.get(); + for (uint32_t i = 0; i < inlinedInfo.getNumberOfFrames(); ++i) { + auto &info = inlinedInfo.getFrame(i); + std::string fnName = (info.FunctionName != "") + ? llvm::demangle(info.FunctionName) + : demangled; + + std::stringstream frameSs; + frameSs << " at " << fnName; + if (info.FileName != "") + frameSs << " [" << info.FileName << ":" << info.Line << "]"; + else + frameSs << " [JIT:0x" << std::hex << entryStart << std::dec << "]"; + + if (mode == StackTraceMode::Debug && i > 0) + frameSs << " (inlined)"; + if (mode == StackTraceMode::Debug) + frameSs << " @ 0x" << std::hex << addr << std::dec; + + frameSs << "\n"; + + // Deduplicate with previous lines + if (currentAddrLines.empty() || currentAddrLines.back() != frameSs.str()) { + currentAddrLines.push_back(frameSs.str()); + } + detailedInfoFound = true; + } + } else { + llvm::consumeError(resOrErr.takeError()); + } + } else { + llvm::consumeError(objOrErr.takeError()); + } + } + + if (!detailedInfoFound) { + std::stringstream frameSs; + frameSs << " at " << demangled << " [JIT:0x" << std::hex + << entryStart << std::dec << "]"; + if (mode == StackTraceMode::Debug) { + frameSs << " [+0x" << std::hex << (addr - entryStart) + << std::dec << "] @ 0x" << std::hex << addr << std::dec; + } + frameSs << "\n"; + currentAddrLines.push_back(frameSs.str()); + } } - frameSs << "\n"; - currentAddrLines.push_back(frameSs.str()); } addrHandled = true; } @@ -166,6 +232,7 @@ void symbolizeStackChain(std::stringstream &ss, if (symbolizeAddr >= 4) symbolizeAddr -= 4; + std::lock_guard sLock(gSymbolizerMutex); auto resOrErr = symbolizer.symbolizeInlinedCode( currentModule, {symbolizeAddr, llvm::object::SectionedAddress::UndefSection}); @@ -216,7 +283,7 @@ void symbolizeStackChain(std::stringstream &ss, bool isInfra = isInfrastructureFrame(demangled, ""); if (!foundFirstUserFrame && !isInfra) foundFirstUserFrame = true; - if (foundFirstUserFrame || mode == StackTraceMode::Debug) { + if ((foundFirstUserFrame && (mode == StackTraceMode::Debug || !isInfra)) || mode == StackTraceMode::Debug) { std::stringstream frameSs; if (!dlSymbolName.empty()) { frameSs << " at " << demangled << " [" @@ -254,7 +321,10 @@ LanguageException::LanguageException(const std::string &name, RTValue message, LanguageException::LanguageException(const LanguageException &other) : name(other.name), message(other.message), payload(other.payload), - capturedStack(other.capturedStack) {} + capturedStack(other.capturedStack) { + retain(message); + retain(payload); +} LanguageException & LanguageException::operator=(const LanguageException &other) { From a04e28d2a1c8e3fcda62461e3d21dc92d0bb9d64 Mon Sep 17 00:00:00 2001 From: Marek Lipert Date: Mon, 23 Mar 2026 22:47:41 +0100 Subject: [PATCH 07/10] Add "form" to exceptions + colours. --- backend-v2/bridge/Exceptions.cpp | 275 +++++++++++++++--- backend-v2/bridge/Exceptions.h | 45 ++- backend-v2/bridge/SourceLocation.h | 22 ++ backend-v2/codegen/CodeGen.cpp | 23 +- backend-v2/codegen/CodeGen.h | 13 +- backend-v2/codegen/invoke/InvokeManager.h | 1 + backend-v2/jit/JITEngine.cpp | 10 +- backend-v2/tests/CMakeLists.txt | 2 + .../tests/codegen/ExceptionColor_test.cpp | 126 ++++++++ .../tests/codegen/ExceptionForm_test.cpp | 118 ++++++++ backend-v2/tools/EdnParser.cpp | 2 +- 11 files changed, 582 insertions(+), 55 deletions(-) create mode 100644 backend-v2/bridge/SourceLocation.h create mode 100644 backend-v2/tests/codegen/ExceptionColor_test.cpp create mode 100644 backend-v2/tests/codegen/ExceptionForm_test.cpp diff --git a/backend-v2/bridge/Exceptions.cpp b/backend-v2/bridge/Exceptions.cpp index 20d19298..d07e30b1 100644 --- a/backend-v2/bridge/Exceptions.cpp +++ b/backend-v2/bridge/Exceptions.cpp @@ -11,27 +11,61 @@ #include #include #include +#include "bytecode.pb.h" #ifdef __APPLE__ #include #endif +#include namespace rt { +struct Colors { + static constexpr const char* RESET = "\033[0m"; + static constexpr const char* BOLD = "\033[1m"; + static constexpr const char* DIM = "\033[2m"; + static constexpr const char* RED = "\033[31m"; + static constexpr const char* GREEN = "\033[32m"; + static constexpr const char* YELLOW = "\033[33m"; + static constexpr const char* BLUE = "\033[34m"; + static constexpr const char* MAGENTA = "\033[35m"; + static constexpr const char* CYAN = "\033[36m"; + static constexpr const char* WHITE = "\033[37m"; + static constexpr const char* BRIGHT_RED = "\033[91m"; + static constexpr const char* BRIGHT_GREEN = "\033[92m"; + static constexpr const char* BRIGHT_YELLOW = "\033[93m"; + static constexpr const char* BRIGHT_BLUE = "\033[94m"; + static constexpr const char* BRIGHT_MAGENTA = "\033[95m"; + static constexpr const char* BRIGHT_CYAN = "\033[96m"; + static constexpr const char* BRIGHT_WHITE = "\033[97m"; + + // Semantic colors + static const char* HEADER(bool e) { return e ? "\033[1;34m" : ""; } // Bold Blue + static const char* MSG(bool e) { return e ? "\033[1m" : ""; } // Bold Default + static const char* LOC(bool e) { return e ? "\033[1;35m" : ""; } // Bold Magenta + static const char* CODE(bool e) { return e ? "\033[1m" : ""; } // Bold Default + static const char* MARK(bool e) { return e ? "\033[1;31m" : ""; } // Bold Red + static const char* USER_FN(bool e) { return e ? "\033[1;32m" : ""; } // Bold Green + static const char* INFRA(bool e) { return e ? "\033[2m" : ""; } // Dim +}; + +inline const char* c(const char* code, bool enabled) { + return enabled ? code : ""; +} + + thread_local std::shared_ptr gCurrentAsyncStack = nullptr; -struct JitFunctionEntry { - uword_t size; - std::string name; - std::vector objectData; -}; + +// JitFunctionEntry and SourceLocation are now defined in Exceptions.h static std::map gJitFunctions; static std::mutex gJitMapMutex; static std::mutex gSymbolizerMutex; void registerJitFunction(uword_t address, uword_t size, const char *name, - const void *objectData, size_t objectSize) { + const void *objectData, size_t objectSize, + std::map locationToForm) { std::lock_guard lock(gJitMapMutex); JitFunctionEntry entry; entry.size = size; @@ -40,13 +74,19 @@ void registerJitFunction(uword_t address, uword_t size, const char *name, entry.objectData.assign(static_cast(objectData), static_cast(objectData) + objectSize); } + entry.locationToForm = std::move(locationToForm); gJitFunctions[address] = std::move(entry); } +void registerJitFunction(uword_t address, uword_t size, const char *name, + const void *objectData, size_t objectSize) { + registerJitFunction(address, size, name, objectData, objectSize, {}); +} + extern "C" void registerJitFunction_C(uword_t address, uword_t size, const char *name, const void *objectData, size_t objectSize) { - registerJitFunction(address, size, name, objectData, objectSize); + registerJitFunction(address, size, name, objectData, objectSize, {}); } static bool isInfrastructureFrame(const std::string &name, @@ -88,7 +128,8 @@ void symbolizeStackChain(std::stringstream &ss, std::shared_ptr stack, llvm::symbolize::LLVMSymbolizer &symbolizer, const std::string &defaultModuleName, - const intptr_t defaultSlide, StackTraceMode mode) { + const intptr_t defaultSlide, StackTraceMode mode, + bool useColor) { auto current = stack; bool isFirstStack = true; std::string lastLine = ""; @@ -150,9 +191,10 @@ void symbolizeStackChain(std::stringstream &ss, : demangled; std::stringstream frameSs; - frameSs << " at " << fnName; + frameSs << " " << Colors::INFRA(useColor) << "at " << c(Colors::RESET, useColor) + << Colors::USER_FN(useColor) << fnName << c(Colors::RESET, useColor); if (info.FileName != "") - frameSs << " [" << info.FileName << ":" << info.Line << "]"; + frameSs << " [" << Colors::LOC(useColor) << info.FileName << ":" << info.Line << c(Colors::RESET, useColor) << "]"; else frameSs << " [JIT:0x" << std::hex << entryStart << std::dec << "]"; @@ -162,6 +204,19 @@ void symbolizeStackChain(std::stringstream &ss, frameSs << " @ 0x" << std::hex << addr << std::dec; frameSs << "\n"; + + // NEW: If this is the first user frame or we're looking for the form, check the locationToForm map + if (!entry.locationToForm.empty() && info.FileName != "") { + SourceLocation loc; + loc.file = info.FileName; + loc.line = info.Line; + loc.column = info.Column; + auto formIt = entry.locationToForm.find(loc); + if (formIt != entry.locationToForm.end()) { + // This is a candidate for the exception's form + // We'll handle this in the constructor's eager symbolization + } + } // Deduplicate with previous lines if (currentAddrLines.empty() || currentAddrLines.back() != frameSs.str()) { @@ -179,8 +234,9 @@ void symbolizeStackChain(std::stringstream &ss, if (!detailedInfoFound) { std::stringstream frameSs; - frameSs << " at " << demangled << " [JIT:0x" << std::hex - << entryStart << std::dec << "]"; + frameSs << " " << c(Colors::DIM, useColor) << "at " << c(Colors::RESET, useColor) + << c(Colors::BRIGHT_GREEN, useColor) << c(Colors::BOLD, useColor) << demangled << c(Colors::RESET, useColor) + << " [JIT:0x" << std::hex << entryStart << std::dec << "]"; if (mode == StackTraceMode::Debug) { frameSs << " [+0x" << std::hex << (addr - entryStart) << std::dec << "] @ 0x" << std::hex << addr << std::dec; @@ -252,9 +308,10 @@ void symbolizeStackChain(std::stringstream &ss, if (foundFirstUserFrame && !isInfra) { std::stringstream frameSs; - frameSs << " at " << demangled; + frameSs << " " << Colors::INFRA(useColor) << "at " << c(Colors::RESET, useColor) + << Colors::USER_FN(useColor) << demangled << c(Colors::RESET, useColor); if (info.FileName != "") - frameSs << " [" << info.FileName << ":" << info.Line << "]"; + frameSs << " [" << Colors::LOC(useColor) << info.FileName << ":" << info.Line << c(Colors::RESET, useColor) << "]"; else frameSs << " [" << currentModule << "]"; frameSs << "\n"; @@ -286,11 +343,13 @@ void symbolizeStackChain(std::stringstream &ss, if ((foundFirstUserFrame && (mode == StackTraceMode::Debug || !isInfra)) || mode == StackTraceMode::Debug) { std::stringstream frameSs; if (!dlSymbolName.empty()) { - frameSs << " at " << demangled << " [" + frameSs << " " << Colors::INFRA(useColor) << "at " << c(Colors::RESET, useColor) + << Colors::INFRA(useColor) << demangled << c(Colors::RESET, useColor) << " [" << (dlinfo.dli_fname ? dlinfo.dli_fname : "unknown") << "]"; } else { - frameSs << " at 0x" << std::hex << addr << std::dec << " [" + frameSs << " " << Colors::INFRA(useColor) << "at " << c(Colors::RESET, useColor) + << "0x" << std::hex << addr << std::dec << " [" << (dlinfo.dli_fname ? dlinfo.dli_fname : "unknown") << "]"; } @@ -317,10 +376,113 @@ LanguageException::LanguageException(const std::string &name, RTValue message, RTValue payload) : name(name), message(message), payload(payload) { capturedStack = captureCurrentStack(); + + // Eager symbolization for form lookup +#ifdef __APPLE__ + char path[1024]; + uint32_t _size = sizeof(path); + _NSGetExecutablePath(path, &_size); +#endif + + bool foundForm = false; + for (uword_t addr : capturedStack->addresses) { + if (addr == 0) continue; + + // Check JIT functions for form + if (!foundForm) { + std::lock_guard lock(gJitMapMutex); + auto it = gJitFunctions.lower_bound(addr); + if (it != gJitFunctions.begin() && + (it == gJitFunctions.end() || it->first > addr)) { + --it; + } + + if (it != gJitFunctions.end()) { + uword_t entryStart = it->first; + const JitFunctionEntry &entry = it->second; + if (addr >= entryStart && addr < entryStart + entry.size) { + if (!entry.objectData.empty()) { + auto buffer = llvm::MemoryBuffer::getMemBuffer( + llvm::StringRef(reinterpret_cast(entry.objectData.data()), + entry.objectData.size()), + entry.name, false); + auto objOrErr = llvm::object::ObjectFile::createObjectFile(buffer->getMemBufferRef()); + if (objOrErr) { + uword_t offset = addr - entryStart; + if (offset >= 4) offset -= 4; + + llvm::symbolize::LLVMSymbolizer localSymbolizer; + auto resOrErr = localSymbolizer.symbolizeInlinedCode( + *objOrErr.get(), + {offset, llvm::object::SectionedAddress::UndefSection}); + + if (resOrErr) { + auto &inlinedInfo = resOrErr.get(); + for (uint32_t i = 0; i < inlinedInfo.getNumberOfFrames(); ++i) { + auto &info = inlinedInfo.getFrame(i); + if (info.FileName != "") { + SourceLocation loc; + loc.file = info.FileName; + loc.line = info.Line; + loc.column = info.Column; + auto formIt = entry.locationToForm.find(loc); + + if (formIt == entry.locationToForm.end() && !entry.locationToForm.empty()) { + // Try a fuzzy match based on basename and line + std::string base = info.FileName; + size_t lastS = base.find_last_of('/'); + if (lastS != std::string::npos) base = base.substr(lastS + 1); + + for (auto const& [k, v] : entry.locationToForm) { + std::string kBase = k.file; + size_t kLastS = kBase.find_last_of('/'); + if (kLastS != std::string::npos) kBase = kBase.substr(kLastS + 1); + + // If line is 0, we take the first match for the file + if (kBase == base && (k.line == (int)info.Line || info.Line == 0)) { + this->form = v; + std::stringstream locSs; + locSs << info.FileName << ":" << k.line << ":" << k.column; + this->sourceLocation = locSs.str(); + foundForm = true; + break; + } + } + } else if (formIt != entry.locationToForm.end()) { + this->form = formIt->second; + std::stringstream locSs; + locSs << info.FileName << ":" << info.Line << ":" << info.Column; + this->sourceLocation = locSs.str(); + foundForm = true; + break; + } + } + } + } else { + llvm::consumeError(resOrErr.takeError()); + } + } else { + llvm::consumeError(objOrErr.takeError()); + } + } + } + } + } + if (foundForm) break; + } +} + +LanguageException::LanguageException(const std::string &name, RTValue message, + RTValue payload, const std::string &form, + const std::string &sourceLocation) + : name(name), message(message), payload(payload), form(form), + sourceLocation(sourceLocation) { + capturedStack = captureCurrentStack(); } LanguageException::LanguageException(const LanguageException &other) : name(other.name), message(other.message), payload(other.payload), + form(other.form), sourceLocation(other.sourceLocation), capturedStack(other.capturedStack) { retain(message); retain(payload); @@ -334,6 +496,8 @@ LanguageException::operator=(const LanguageException &other) { name = other.name; message = other.message; payload = other.payload; + form = other.form; + sourceLocation = other.sourceLocation; capturedStack = other.capturedStack; retain(message); retain(payload); @@ -349,22 +513,43 @@ LanguageException::~LanguageException() noexcept { std::string LanguageException::toString(llvm::symbolize::LLVMSymbolizer &symbolizer, const std::string &moduleName, const intptr_t slide, - StackTraceMode mode) const { + StackTraceMode mode, bool useColor) const { std::stringstream ss; - ss << "Exception: " << name << "\n"; + + if (useColor) { + const char* force = getenv("CLOJURE_RT_FORCE_COLOR"); + if (force && std::string(force) == "1") { + useColor = true; + } else { + useColor = isatty(STDERR_FILENO); + } + } + + ss << "\n " << Colors::MARK(useColor) << "[!] " << c(Colors::RESET, useColor) + << Colors::HEADER(useColor) << "Exception: " << name << c(Colors::RESET, useColor) << "\n"; retain(message); String *msgStr = String_compactify(::toString(message)); - ss << "Message: " << String_c_str(msgStr) << "\n"; + ss << " " << Colors::MSG(useColor) + << "Message: " << String_c_str(msgStr) << c(Colors::RESET, useColor) << "\n"; Ptr_release(msgStr); retain(payload); - String *payStr = String_compactify(::toString(payload)); - ss << "Payload: " << (payload == 0 ? "nil" : String_c_str(payStr)) << "\n"; - Ptr_release(payStr); + if (payload != 0) { + String *payStr = String_compactify(::toString(payload)); + ss << " " << c(Colors::WHITE, useColor) << "Payload: " << String_c_str(payStr) << c(Colors::RESET, useColor) << "\n"; + Ptr_release(payStr); + } - ss << "Stack Trace:\n"; - symbolizeStackChain(ss, capturedStack, symbolizer, moduleName, slide, mode); + if (!form.empty()) { + ss << "\n " << c(Colors::BOLD, useColor) << "Source context:" << c(Colors::RESET, useColor) << "\n"; + ss << " " << Colors::LOC(useColor) << sourceLocation << c(Colors::RESET, useColor) << ":\n"; + ss << " " << Colors::CODE(useColor) << form << c(Colors::RESET, useColor) << "\n"; + ss << " " << Colors::MARK(useColor) << std::string(form.length(), '^') << c(Colors::RESET, useColor) << "\n"; + } + + ss << "\n " << c(Colors::BOLD, useColor) << "Stack Trace:" << c(Colors::RESET, useColor) << "\n"; + symbolizeStackChain(ss, capturedStack, symbolizer, moduleName, slide, mode, useColor); return ss.str(); } @@ -392,7 +577,7 @@ void printReferenceCounts() { } std::string getExceptionString(const LanguageException &e, - StackTraceMode mode) { + StackTraceMode mode, bool useColor) { static llvm::symbolize::LLVMSymbolizer symbolizer; #ifdef __APPLE__ @@ -412,10 +597,9 @@ std::string getExceptionString(const LanguageException &e, intptr_t slide = 0; #endif - return e.toString(symbolizer, moduleName, slide, mode); + return e.toString(symbolizer, moduleName, slide, mode, useColor); } -} // namespace rt void throwInternalInconsistencyException(const std::string &errorMessage) { throw rt::LanguageException( @@ -486,14 +670,35 @@ extern "C" void throwIndexOutOfBoundsException_C(uword_t index, uword_t count) { } void throwCodeGenerationException(const std::string &errorMessage, - const Node &node) { + const std::string &form, + const std::string &file, + int line, int column) { std::stringstream retval; - auto env = node.env(); - retval << env.file() << ":" << env.line() << ":" << env.column() + retval << file << ":" << line << ":" << column << ": error: " << errorMessage << "\n"; - retval << node.form() << "\n"; - retval << std::string(node.form().length(), '^') << "\n"; - throw rt::LanguageException( - "CodeGenerationException", - RT_boxPtr(::String_createDynamicStr(retval.str().c_str())), RT_boxNil()); + retval << form << "\n"; + retval << std::string(form.length(), '^') << "\n"; + + std::stringstream locSs; + locSs << file << ":" << line << ":" << column; + + throw rt::LanguageException("CodeGenerationException", + RT_boxPtr(::String_createDynamicStr(retval.str().c_str())), + RT_boxNil(), + form, locSs.str()); } + +void throwCodeGenerationException(const std::string &errorMessage, + const clojure::rt::protobuf::bytecode::Node &node) { + std::string file = "unknown"; + int line = 0; + int column = 0; + if (node.has_env()) { + file = node.env().file(); + line = node.env().line(); + column = node.env().column(); + } + throwCodeGenerationException(errorMessage, node.form(), file, line, column); +} + +} // namespace rt diff --git a/backend-v2/bridge/Exceptions.h b/backend-v2/bridge/Exceptions.h index cab4e054..57349606 100644 --- a/backend-v2/bridge/Exceptions.h +++ b/backend-v2/bridge/Exceptions.h @@ -1,7 +1,7 @@ #ifndef RT_EXCEPTIONS #define RT_EXCEPTIONS -#include "bytecode.pb.h" + #include "llvm/DebugInfo/Symbolize/Symbolize.h" #include "llvm/Support/Error.h" #include @@ -14,15 +14,28 @@ #include #include #include +#include "SourceLocation.h" + -using namespace clojure::rt::protobuf::bytecode; + +namespace clojure { namespace rt { namespace protobuf { namespace bytecode { class Node; } } } } namespace rt { enum class StackTraceMode { Friendly, Debug }; + + +struct JitFunctionEntry { + uword_t size; + std::string name; + std::vector objectData; + std::map locationToForm; +}; + struct CapturedStack { std::vector addresses; + std::vector symbolizedFrames; std::shared_ptr parent; }; @@ -34,10 +47,14 @@ class LanguageException : public std::exception { std::string name; RTValue message; RTValue payload; + std::string form; + std::string sourceLocation; std::shared_ptr capturedStack; public: LanguageException(const std::string &name, RTValue message, RTValue payload); + LanguageException(const std::string &name, RTValue message, RTValue payload, + const std::string &form, const std::string &sourceLocation); LanguageException(const LanguageException &other); LanguageException &operator=(const LanguageException &other); ~LanguageException() noexcept override; @@ -46,24 +63,36 @@ class LanguageException : public std::exception { std::string toString(llvm::symbolize::LLVMSymbolizer &symbolizer, const std::string &moduleName = "JITMemoryBuffer", const intptr_t slide = 0x0, - StackTraceMode mode = StackTraceMode::Friendly) const; + StackTraceMode mode = StackTraceMode::Friendly, + bool useColor = true) const; const std::string &getName() const { return name; } RTValue getMessage() const { return message; } RTValue getPayload() const { return payload; } + const std::string &getForm() const { return form; } + const std::string &getSourceLocation() const { return sourceLocation; } std::shared_ptr getCapturedStack() const { return capturedStack; } }; std::string getExceptionString(const LanguageException &e, - StackTraceMode mode = StackTraceMode::Friendly); - -} // namespace rt + StackTraceMode mode = StackTraceMode::Friendly, + bool useColor = true); -// C++-only functions (no extern "C" needed/possible due to std::string/Node) [[noreturn]] void throwInternalInconsistencyException(const std::string &errorMessage); [[noreturn]] void throwCodeGenerationException(const std::string &errorMessage, - const Node &node); + const std::string &form, + const std::string &file, + int line, int column); +[[noreturn]] void throwCodeGenerationException(const std::string &errorMessage, + const clojure::rt::protobuf::bytecode::Node &node); + +// New registration function with form mapping +void registerJitFunction(uword_t addr, size_t size, const char *name, + const void *objData, size_t objSize, + std::map locationToForm); + +} // namespace rt extern "C" void registerJitFunction_C(uword_t addr, size_t size, const char *name, const void *objData, diff --git a/backend-v2/bridge/SourceLocation.h b/backend-v2/bridge/SourceLocation.h new file mode 100644 index 00000000..2ff1bc1a --- /dev/null +++ b/backend-v2/bridge/SourceLocation.h @@ -0,0 +1,22 @@ +#ifndef RT_SOURCE_LOCATION_H +#define RT_SOURCE_LOCATION_H + +#include + +namespace rt { + +struct SourceLocation { + std::string file; + int line; + int column; + + bool operator<(const SourceLocation &other) const { + if (file != other.file) return file < other.file; + if (line != other.line) return line < other.line; + return column < other.column; + } +}; + +} // namespace rt + +#endif diff --git a/backend-v2/codegen/CodeGen.cpp b/backend-v2/codegen/CodeGen.cpp index 37563ef1..ded5a981 100644 --- a/backend-v2/codegen/CodeGen.cpp +++ b/backend-v2/codegen/CodeGen.cpp @@ -6,6 +6,7 @@ #include "runtime/Object.h" #include "runtime/RTValue.h" #include "runtime/String.h" +#include "../bridge/SourceLocation.h" using namespace llvm; using namespace clojure::rt::protobuf::bytecode; @@ -22,7 +23,7 @@ CodeGenResult CodeGen::release() && { DIB->finalize(); auto constants = std::move(generatedConstants); generatedConstants.clear(); // Ensure destructor doesn't re-release - return {std::move(TSContext), std::move(TheModule), std::move(constants)}; + return {std::move(TSContext), std::move(TheModule), std::move(constants), std::move(formMap)}; } std::string CodeGen::codegenTopLevel(const Node &node) { @@ -177,6 +178,14 @@ TypedValue CodeGen::codegen(const Node &node, &node.unwindmemory()); auto env = node.env(); + + // Record form for exception reporting + SourceLocation loc; + loc.file = env.file(); + loc.line = env.line(); + loc.column = env.column(); + formMap[loc] = node.form(); + if (!LexicalBlocks.empty()) { Builder.SetCurrentDebugLocation(llvm::DILocation::get( TheContext, env.line(), env.column(), LexicalBlocks.back())); @@ -275,11 +284,13 @@ TypedValue CodeGen::codegen(const Node &node, return codegen(node, node.subnode().var(), typeRestrictions); case opWithMeta: return codegen(node, node.subnode().withmeta(), typeRestrictions); - default: + default: { + auto env = node.env(); throwCodeGenerationException( std::string("Compiler does not support the following op yet: ") + Op_Name(node.op()), - node); + node.form(), env.file(), env.line(), env.column()); + } } return TypedValue(ObjectTypeSet::all(), nullptr); } @@ -375,11 +386,13 @@ ObjectTypeSet CodeGen::getType(const Node &node, return getType(node, node.subnode().var(), typeRestrictions); case opWithMeta: return getType(node, node.subnode().withmeta(), typeRestrictions); - default: + default: { + auto env = node.env(); throwCodeGenerationException( std::string("Compiler does not support the following op yet: ") + Op_Name(node.op()), - node); + node.form(), env.file(), env.line(), env.column()); + } } return ObjectTypeSet::all(); } diff --git a/backend-v2/codegen/CodeGen.h b/backend-v2/codegen/CodeGen.h index 6d732a68..b883f63b 100644 --- a/backend-v2/codegen/CodeGen.h +++ b/backend-v2/codegen/CodeGen.h @@ -12,10 +12,11 @@ #include #include #include - -#include "TypedValue.h" #include #include +#include "../bridge/SourceLocation.h" + +#include "TypedValue.h" #include "CleanupChainGuard.h" @@ -35,6 +36,7 @@ struct CodeGenResult { std::unique_ptr context; std::unique_ptr module; std::vector constants; + std::map formMap; }; class CodeGen { @@ -91,6 +93,10 @@ class CodeGen { CodeGenResult release() &&; + const std::map &getFormMap() const { + return formMap; + } + std::string codegenTopLevel(const Node &node); std::string generateInstanceCallBridge( const std::string &methodName, @@ -184,6 +190,9 @@ class CodeGen { } MemoryManagement &getMemoryManagement() { return memoryManagement; } DynamicConstructor &getDynamicConstructor() { return dynamicConstructor; } + +private: + std::map formMap; }; } // namespace rt diff --git a/backend-v2/codegen/invoke/InvokeManager.h b/backend-v2/codegen/invoke/InvokeManager.h index 200e21d2..52805553 100644 --- a/backend-v2/codegen/invoke/InvokeManager.h +++ b/backend-v2/codegen/invoke/InvokeManager.h @@ -11,6 +11,7 @@ #include #include #include +#include "bytecode.pb.h" #include "../../types/ObjectTypeSet.h" #include "../LLVMTypes.h" diff --git a/backend-v2/jit/JITEngine.cpp b/backend-v2/jit/JITEngine.cpp index 94651202..ae0741fc 100644 --- a/backend-v2/jit/JITEngine.cpp +++ b/backend-v2/jit/JITEngine.cpp @@ -6,6 +6,7 @@ #include "../tools/EdnParser.h" #include "bridge/Exceptions.h" #include "bridge/InstanceCallStub.h" +#include "bridge/SourceLocation.h" #include "llvm/IR/MDBuilder.h" #include "llvm/Transforms/IPO/GlobalDCE.h" #include @@ -216,6 +217,7 @@ JITEngine::compileGeneric(std::function codegenFunc, auto context = std::move(result.context); auto module = std::move(result.module); auto constants = std::move(result.constants); + auto formMap = std::move(result.formMap); this->optimize(*module, level, fName); @@ -277,10 +279,10 @@ JITEngine::compileGeneric(std::function codegenFunc, auto &captured = this->capturedObjectBuffers[foundIdx]; // printf("JIT: Found buffer for %s (size %zu)\n", // fName.c_str(), captured.buffer->getBufferSize()); - registerJitFunction_C( + registerJitFunction( Sym->getValue(), captured.buffer->getBufferSize(), fName.c_str(), captured.buffer->getBufferStart(), - captured.buffer->getBufferSize()); + captured.buffer->getBufferSize(), std::move(formMap)); this->capturedObjectBuffers.erase( this->capturedObjectBuffers.begin() + foundIdx); } else { @@ -289,8 +291,8 @@ JITEngine::compileGeneric(std::function codegenFunc, // this->capturedObjectBuffers.size()); Fallback for cases // where symbol might not be in the dynamic symbol table of // the object (though for JIT it usually is). - registerJitFunction_C(Sym->getValue(), 1024 * 1024, - fName.c_str(), nullptr, 0); + registerJitFunction(Sym->getValue(), 1024 * 1024, + fName.c_str(), nullptr, 0, std::move(formMap)); } } diff --git a/backend-v2/tests/CMakeLists.txt b/backend-v2/tests/CMakeLists.txt index ff385249..c00157ac 100644 --- a/backend-v2/tests/CMakeLists.txt +++ b/backend-v2/tests/CMakeLists.txt @@ -24,6 +24,8 @@ set(TEST_MODULES codegen/O3_DebugInfo_test codegen/AsyncStackTrace_test codegen/DeepStackSymbolization_test + codegen/ExceptionForm_test + codegen/ExceptionColor_test VarUAF_repro_test JITSafety_test ) diff --git a/backend-v2/tests/codegen/ExceptionColor_test.cpp b/backend-v2/tests/codegen/ExceptionColor_test.cpp new file mode 100644 index 00000000..fed201be --- /dev/null +++ b/backend-v2/tests/codegen/ExceptionColor_test.cpp @@ -0,0 +1,126 @@ +#include "../../jit/JITEngine.h" +#include "../../state/ThreadsafeCompilerState.h" +#include "../../tools/EdnParser.h" +#include "bytecode.pb.h" +#include +#include +#include + +extern "C" { +#include "../../runtime/RuntimeInterface.h" +#include "../../runtime/tests/TestTools.h" +#include +} + +#include "../../bridge/Exceptions.h" + +using namespace std; +using namespace rt; +using namespace clojure::rt::protobuf::bytecode; + +static RTValue resPtrToValue(llvm::orc::ExecutorAddr res) { + return res.toPtr()(); +} + +static void setup_compiler_state(rt::ThreadsafeCompilerState &compState, rt::JITEngine &engine) { + // Load metadata + Programme astClasses; + { + std::fstream classesInput("tests/rt-classes.cljb", + std::ios::in | std::ios::binary); + if (!astClasses.ParseFromIstream(&classesInput)) { + fail_msg("Failed to parse bytecode."); + } + } + + // Initialize compiler state with metadata + llvm::orc::ExecutorAddr resClasses = + engine + .compileAST(astClasses.nodes(0), "__classes", + llvm::OptimizationLevel::O0, false) + .get().address; + RTValue classes = resClasses.toPtr()(); + auto classesList = rt::buildClasses(classes); + for (auto &desc : classesList) { + auto &nameStr = desc->name; + String *className = String_create(nameStr.c_str()); + Ptr_retain(className); + Class *cls = Class_create(className, className, 0, nullptr); + cls->compilerExtension = desc.release(); + cls->compilerExtensionDestructor = delete_class_description; + compState.classRegistry.registerObject(nameStr.c_str(), cls); + } +} + +static void test_exception_color_output(void **state) { + (void)state; + // We can't easily test isatty(STDERR_FILENO) in a test, + // but we can verify that the code compiles and runs. + // We'll also check if the layout changes are present. + + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + setup_compiler_state(compState, engine); + + Node callNode; + callNode.set_op(opStaticCall); + callNode.set_tag("long"); + callNode.set_form("(/ 1 0)"); + callNode.mutable_env()->set_file("color_test.clj"); + callNode.mutable_env()->set_line(10); + callNode.mutable_env()->set_column(5); + + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("divide"); + + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->set_tag("long"); + arg1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg1->mutable_subnode()->mutable_const_()->set_val("1"); + + auto *arg2 = sc->add_args(); + arg2->set_op(opConst); + arg2->set_tag("long"); + arg2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg2->mutable_subnode()->mutable_const_()->set_val("0"); + + auto resCall = engine + .compileAST(callNode, "__test_exception_color", + llvm::OptimizationLevel::O0, false) + .get().address; + + try { + resPtrToValue(resCall); + fail_msg("Should have thrown ArithmeticException"); + } catch (const LanguageException &e) { + // Force color off for string comparison if needed, but here we just print it + std::string err = getExceptionString(e, StackTraceMode::Friendly, true); + + cout << "--- START OF EXCEPTION OUTPUT ---" << endl; + cout << err << endl; + cout << "--- END OF EXCEPTION OUTPUT ---" << endl; + + // Verify layout markers + assert_true(err.find("[!]") != std::string::npos); + assert_true(err.find("Exception:") != std::string::npos); + assert_true(err.find("Message:") != std::string::npos); + assert_true(err.find("Source context:") != std::string::npos); + assert_true(err.find("^^^^^^^") != std::string::npos); + assert_true(err.find("Stack Trace:") != std::string::npos); + } + }); +} + +int main(void) { + initialise_memory(); + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_exception_color_output), + }; + + int result = cmocka_run_group_tests(tests, NULL, NULL); + RuntimeInterface_cleanup(); + return result; +} diff --git a/backend-v2/tests/codegen/ExceptionForm_test.cpp b/backend-v2/tests/codegen/ExceptionForm_test.cpp new file mode 100644 index 00000000..1b3d1c95 --- /dev/null +++ b/backend-v2/tests/codegen/ExceptionForm_test.cpp @@ -0,0 +1,118 @@ +#include "../../RuntimeHeaders.h" +#include "../../jit/JITEngine.h" +#include "../../state/ThreadsafeCompilerState.h" +#include "../../tools/EdnParser.h" +#include "bytecode.pb.h" +#include +#include +#include + +extern "C" { +#include "../../runtime/RuntimeInterface.h" +#include "../../runtime/tests/TestTools.h" +#include +} + +#include "../../bridge/Exceptions.h" + +using namespace std; +using namespace rt; +using namespace clojure::rt::protobuf::bytecode; + +static RTValue resPtrToValue(llvm::orc::ExecutorAddr res) { + return res.toPtr()(); +} + +static void setup_compiler_state(rt::ThreadsafeCompilerState &compState, rt::JITEngine &engine) { + // Load metadata + Programme astClasses; + { + std::fstream classesInput("tests/rt-classes.cljb", + std::ios::in | std::ios::binary); + if (!astClasses.ParseFromIstream(&classesInput)) { + fail_msg("Failed to parse bytecode."); + } + } + + // Initialize compiler state with metadata + llvm::orc::ExecutorAddr resClasses = + engine + .compileAST(astClasses.nodes(0), "__classes", + llvm::OptimizationLevel::O0, false) + .get().address; + RTValue classes = resClasses.toPtr()(); + auto classesList = rt::buildClasses(classes); + for (auto &desc : classesList) { + auto &nameStr = desc->name; + String *className = String_create(nameStr.c_str()); + Ptr_retain(className); + Class *cls = Class_create(className, className, 0, nullptr); + cls->compilerExtension = desc.release(); + cls->compilerExtensionDestructor = delete_class_description; + compState.classRegistry.registerObject(nameStr.c_str(), cls); + } +} + +static void test_exception_form_capture(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + setup_compiler_state(compState, engine); + + // Create a StaticCallNode for (/ 1 0) to trigger ArithmeticException + Node callNode; + callNode.set_op(opStaticCall); + callNode.set_tag("long"); + callNode.set_form("(/ 1 0)"); + callNode.mutable_env()->set_file("test_file.clj"); + callNode.mutable_env()->set_line(42); + callNode.mutable_env()->set_column(10); + + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("divide"); // Numbers/divide throws ArithmeticException for div by zero + + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->set_tag("long"); + arg1->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + arg1->mutable_subnode()->mutable_const_()->set_val("1"); + + auto *arg2 = sc->add_args(); + arg2->set_op(opConst); + arg2->set_tag("long"); + arg2->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + arg2->mutable_subnode()->mutable_const_()->set_val("0"); + + auto resCall = engine + .compileAST(callNode, "__test_exception_form", + llvm::OptimizationLevel::O0, false) + .get().address; + + try { + resPtrToValue(resCall); + fail_msg("Should have thrown ArithmeticException"); + } catch (const LanguageException &e) { + std::string err = getExceptionString(e); + cout << "Caught expected exception with form info:\n" << err << endl; + // Verify that the form and location are present in the error string + assert_true(err.find("(/ 1 0)") != std::string::npos); + assert_true(err.find("test_file.clj:42:10") != std::string::npos); + assert_true(err.find("Source context:") != std::string::npos); + } + }); +} + +int main(void) { + initialise_memory(); + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_exception_form_capture), + }; + + int result = cmocka_run_group_tests(tests, NULL, NULL); + RuntimeInterface_cleanup(); + return result; +} diff --git a/backend-v2/tools/EdnParser.cpp b/backend-v2/tools/EdnParser.cpp index 991f5075..872d1f6d 100644 --- a/backend-v2/tools/EdnParser.cpp +++ b/backend-v2/tools/EdnParser.cpp @@ -1,6 +1,7 @@ #include "EdnParser.h" #include "RTValueWrapper.h" #include "bridge/Exceptions.h" +#include namespace rt { @@ -579,7 +580,6 @@ IntrinsicDescription::IntrinsicDescription(RTValue from, } } -#include vector> buildClasses(RTValue from) { // from is consumed by TemporaryClassData TemporaryClassData classData(from); From 7aa6b51667e6d37a87f689e5fa32786beb1362f5 Mon Sep 17 00:00:00 2001 From: Marek Lipert Date: Tue, 24 Mar 2026 07:52:30 +0100 Subject: [PATCH 08/10] LongJmp --- backend-v2/tests/JITSafety_test.cpp | 3 +++ backend-v2/tests/VarUAF_repro_test.cpp | 3 +++ backend-v2/tests/codegen/AsyncStackTrace_test.cpp | 3 +++ backend-v2/tests/codegen/DCE_test.cpp | 3 +++ backend-v2/tests/codegen/DeepStackSymbolization_test.cpp | 3 +++ backend-v2/tests/codegen/DoNode_test.cpp | 3 +++ backend-v2/tests/codegen/DoubleAddRepro_test.cpp | 3 +++ backend-v2/tests/codegen/ExceptionColor_test.cpp | 3 +++ backend-v2/tests/codegen/ExceptionForm_test.cpp | 3 +++ backend-v2/tests/codegen/IfNode_test.cpp | 3 +++ backend-v2/tests/codegen/InstanceCallIC_test.cpp | 3 +++ backend-v2/tests/codegen/InstanceCallNode_test.cpp | 3 +++ backend-v2/tests/codegen/JITArithmetic_test.cpp | 3 +++ backend-v2/tests/codegen/LetNode_test.cpp | 3 +++ backend-v2/tests/codegen/MathIntrinsicsRepro_test.cpp | 3 +++ backend-v2/tests/codegen/MathIntrinsics_test.cpp | 3 +++ backend-v2/tests/codegen/NoThrow_test.cpp | 3 +++ backend-v2/tests/codegen/O3_DebugInfo_test.cpp | 3 +++ backend-v2/tests/codegen/Overflow_test.cpp | 3 +++ backend-v2/tests/codegen/StaticCallNode_test.cpp | 3 +++ backend-v2/tests/codegen/StaticFieldNode_test.cpp | 3 +++ backend-v2/tests/codegen/VoidInvokeRepro_test.cpp | 3 +++ 22 files changed, 66 insertions(+) diff --git a/backend-v2/tests/JITSafety_test.cpp b/backend-v2/tests/JITSafety_test.cpp index bbf8716c..39bdbf35 100644 --- a/backend-v2/tests/JITSafety_test.cpp +++ b/backend-v2/tests/JITSafety_test.cpp @@ -10,6 +10,9 @@ extern "C" { #include "../runtime/RuntimeInterface.h" #include "../runtime/tests/TestTools.h" #include "../bridge/InstanceCallStub.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/VarUAF_repro_test.cpp b/backend-v2/tests/VarUAF_repro_test.cpp index 973f6c5c..10bc0e17 100644 --- a/backend-v2/tests/VarUAF_repro_test.cpp +++ b/backend-v2/tests/VarUAF_repro_test.cpp @@ -11,6 +11,9 @@ extern "C" { #include "../runtime/RuntimeInterface.h" #include "../runtime/tests/TestTools.h" #include "../runtime/Var.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/codegen/AsyncStackTrace_test.cpp b/backend-v2/tests/codegen/AsyncStackTrace_test.cpp index 95338aed..9bfb86c7 100644 --- a/backend-v2/tests/codegen/AsyncStackTrace_test.cpp +++ b/backend-v2/tests/codegen/AsyncStackTrace_test.cpp @@ -3,6 +3,9 @@ #include "../../jit/JITEngine.h" #include "../../state/ThreadsafeCompilerState.h" #include "bytecode.pb.h" +#include +#include +#include #include #include #include diff --git a/backend-v2/tests/codegen/DCE_test.cpp b/backend-v2/tests/codegen/DCE_test.cpp index 036fe9ef..117ee01a 100644 --- a/backend-v2/tests/codegen/DCE_test.cpp +++ b/backend-v2/tests/codegen/DCE_test.cpp @@ -14,6 +14,9 @@ extern "C" { #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/codegen/DeepStackSymbolization_test.cpp b/backend-v2/tests/codegen/DeepStackSymbolization_test.cpp index 11baf530..e7b95b25 100644 --- a/backend-v2/tests/codegen/DeepStackSymbolization_test.cpp +++ b/backend-v2/tests/codegen/DeepStackSymbolization_test.cpp @@ -12,6 +12,9 @@ extern "C" { #include "../../runtime/Keyword.h" #include "../../runtime/Var.h" #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/codegen/DoNode_test.cpp b/backend-v2/tests/codegen/DoNode_test.cpp index 44b681de..b6925be4 100644 --- a/backend-v2/tests/codegen/DoNode_test.cpp +++ b/backend-v2/tests/codegen/DoNode_test.cpp @@ -8,6 +8,9 @@ extern "C" { #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/codegen/DoubleAddRepro_test.cpp b/backend-v2/tests/codegen/DoubleAddRepro_test.cpp index a77620f7..c49cd97d 100644 --- a/backend-v2/tests/codegen/DoubleAddRepro_test.cpp +++ b/backend-v2/tests/codegen/DoubleAddRepro_test.cpp @@ -12,6 +12,9 @@ extern "C" { #include "../../runtime/BigInteger.h" #include "../../runtime/String.h" #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include void delete_class_description(void *ptr); diff --git a/backend-v2/tests/codegen/ExceptionColor_test.cpp b/backend-v2/tests/codegen/ExceptionColor_test.cpp index fed201be..3bb710a4 100644 --- a/backend-v2/tests/codegen/ExceptionColor_test.cpp +++ b/backend-v2/tests/codegen/ExceptionColor_test.cpp @@ -9,6 +9,9 @@ extern "C" { #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/codegen/ExceptionForm_test.cpp b/backend-v2/tests/codegen/ExceptionForm_test.cpp index 1b3d1c95..6b2eaee7 100644 --- a/backend-v2/tests/codegen/ExceptionForm_test.cpp +++ b/backend-v2/tests/codegen/ExceptionForm_test.cpp @@ -10,6 +10,9 @@ extern "C" { #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/codegen/IfNode_test.cpp b/backend-v2/tests/codegen/IfNode_test.cpp index 15f10668..ecc62c74 100644 --- a/backend-v2/tests/codegen/IfNode_test.cpp +++ b/backend-v2/tests/codegen/IfNode_test.cpp @@ -10,6 +10,9 @@ extern "C" { #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/codegen/InstanceCallIC_test.cpp b/backend-v2/tests/codegen/InstanceCallIC_test.cpp index fa3ba50f..8fa4f377 100644 --- a/backend-v2/tests/codegen/InstanceCallIC_test.cpp +++ b/backend-v2/tests/codegen/InstanceCallIC_test.cpp @@ -14,6 +14,9 @@ extern "C" { #include "../../runtime/String.h" #include "../../runtime/Var.h" #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/codegen/InstanceCallNode_test.cpp b/backend-v2/tests/codegen/InstanceCallNode_test.cpp index a4108e5c..4d213026 100644 --- a/backend-v2/tests/codegen/InstanceCallNode_test.cpp +++ b/backend-v2/tests/codegen/InstanceCallNode_test.cpp @@ -9,6 +9,9 @@ extern "C" { #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/codegen/JITArithmetic_test.cpp b/backend-v2/tests/codegen/JITArithmetic_test.cpp index b2735a55..3f9258c7 100644 --- a/backend-v2/tests/codegen/JITArithmetic_test.cpp +++ b/backend-v2/tests/codegen/JITArithmetic_test.cpp @@ -10,6 +10,9 @@ extern "C" { #include "../../runtime/tests/TestTools.h" #include "../../runtime/BigInteger.h" #include "../../runtime/String.h" +#include +#include +#include #include void delete_class_description(void *ptr); diff --git a/backend-v2/tests/codegen/LetNode_test.cpp b/backend-v2/tests/codegen/LetNode_test.cpp index 70288719..aa66782b 100644 --- a/backend-v2/tests/codegen/LetNode_test.cpp +++ b/backend-v2/tests/codegen/LetNode_test.cpp @@ -8,6 +8,9 @@ extern "C" { #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/codegen/MathIntrinsicsRepro_test.cpp b/backend-v2/tests/codegen/MathIntrinsicsRepro_test.cpp index 4ed95a4c..d9ae19f3 100644 --- a/backend-v2/tests/codegen/MathIntrinsicsRepro_test.cpp +++ b/backend-v2/tests/codegen/MathIntrinsicsRepro_test.cpp @@ -9,6 +9,9 @@ extern "C" { #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/codegen/MathIntrinsics_test.cpp b/backend-v2/tests/codegen/MathIntrinsics_test.cpp index 87fd6c8c..59b99263 100644 --- a/backend-v2/tests/codegen/MathIntrinsics_test.cpp +++ b/backend-v2/tests/codegen/MathIntrinsics_test.cpp @@ -13,6 +13,9 @@ extern "C" { #include "../../runtime/BigInteger.h" #include "../../runtime/String.h" #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include void delete_class_description(void *ptr); diff --git a/backend-v2/tests/codegen/NoThrow_test.cpp b/backend-v2/tests/codegen/NoThrow_test.cpp index 72d255d9..cf8c73ab 100644 --- a/backend-v2/tests/codegen/NoThrow_test.cpp +++ b/backend-v2/tests/codegen/NoThrow_test.cpp @@ -8,6 +8,9 @@ extern "C" { #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/codegen/O3_DebugInfo_test.cpp b/backend-v2/tests/codegen/O3_DebugInfo_test.cpp index 489fd81d..8a0c2a10 100644 --- a/backend-v2/tests/codegen/O3_DebugInfo_test.cpp +++ b/backend-v2/tests/codegen/O3_DebugInfo_test.cpp @@ -10,6 +10,9 @@ extern "C" { #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/codegen/Overflow_test.cpp b/backend-v2/tests/codegen/Overflow_test.cpp index 2895a837..167d39e4 100644 --- a/backend-v2/tests/codegen/Overflow_test.cpp +++ b/backend-v2/tests/codegen/Overflow_test.cpp @@ -10,6 +10,9 @@ extern "C" { #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/codegen/StaticCallNode_test.cpp b/backend-v2/tests/codegen/StaticCallNode_test.cpp index 3eac6c6e..968deea8 100644 --- a/backend-v2/tests/codegen/StaticCallNode_test.cpp +++ b/backend-v2/tests/codegen/StaticCallNode_test.cpp @@ -9,6 +9,9 @@ extern "C" { #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/codegen/StaticFieldNode_test.cpp b/backend-v2/tests/codegen/StaticFieldNode_test.cpp index 4fa85154..9326e0d4 100644 --- a/backend-v2/tests/codegen/StaticFieldNode_test.cpp +++ b/backend-v2/tests/codegen/StaticFieldNode_test.cpp @@ -9,6 +9,9 @@ extern "C" { #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include } diff --git a/backend-v2/tests/codegen/VoidInvokeRepro_test.cpp b/backend-v2/tests/codegen/VoidInvokeRepro_test.cpp index 789c9074..259d2722 100644 --- a/backend-v2/tests/codegen/VoidInvokeRepro_test.cpp +++ b/backend-v2/tests/codegen/VoidInvokeRepro_test.cpp @@ -6,6 +6,9 @@ extern "C" { #include "../../runtime/tests/TestTools.h" +#include +#include +#include #include } From e45005529b85cac003c411077684b8051db84eb6 Mon Sep 17 00:00:00 2001 From: Marek Lipert Date: Tue, 24 Mar 2026 08:07:56 +0100 Subject: [PATCH 09/10] Includes --- backend-v2/tests/JITSafety_test.cpp | 4 +++- backend-v2/tests/VarUAF_repro_test.cpp | 4 +++- backend-v2/tests/codegen/AsyncStackTrace_test.cpp | 8 ++++---- backend-v2/tests/codegen/DCE_test.cpp | 4 +++- backend-v2/tests/codegen/DeepStackSymbolization_test.cpp | 4 +++- backend-v2/tests/codegen/DoNode_test.cpp | 5 +++-- backend-v2/tests/codegen/DoubleAddRepro_test.cpp | 4 +++- backend-v2/tests/codegen/IfNode_test.cpp | 5 +++-- backend-v2/tests/codegen/InstanceCallIC_test.cpp | 5 +++-- backend-v2/tests/codegen/InstanceCallNode_test.cpp | 4 ++-- backend-v2/tests/codegen/JITArithmetic_test.cpp | 4 +++- backend-v2/tests/codegen/LetNode_test.cpp | 5 +++-- backend-v2/tests/codegen/MathIntrinsicsRepro_test.cpp | 3 ++- backend-v2/tests/codegen/MathIntrinsics_test.cpp | 4 +++- backend-v2/tests/codegen/NoThrow_test.cpp | 4 +++- backend-v2/tests/codegen/O3_DebugInfo_test.cpp | 4 +++- backend-v2/tests/codegen/Overflow_test.cpp | 4 +++- backend-v2/tests/codegen/StaticCallNode_test.cpp | 4 ++-- backend-v2/tests/codegen/StaticCallType_test.cpp | 4 +++- backend-v2/tests/codegen/StaticFieldNode_test.cpp | 3 ++- backend-v2/tests/codegen/VoidInvokeRepro_test.cpp | 3 ++- backend-v2/tests/tools/EdnParser_test.cpp | 2 +- 22 files changed, 60 insertions(+), 31 deletions(-) diff --git a/backend-v2/tests/JITSafety_test.cpp b/backend-v2/tests/JITSafety_test.cpp index 39bdbf35..74c4d870 100644 --- a/backend-v2/tests/JITSafety_test.cpp +++ b/backend-v2/tests/JITSafety_test.cpp @@ -6,15 +6,17 @@ #include #include -extern "C" { #include "../runtime/RuntimeInterface.h" #include "../runtime/tests/TestTools.h" #include "../bridge/InstanceCallStub.h" #include #include #include +extern "C" { #include } +} +} using namespace rt; diff --git a/backend-v2/tests/VarUAF_repro_test.cpp b/backend-v2/tests/VarUAF_repro_test.cpp index 10bc0e17..547bd185 100644 --- a/backend-v2/tests/VarUAF_repro_test.cpp +++ b/backend-v2/tests/VarUAF_repro_test.cpp @@ -7,15 +7,17 @@ #include #include -extern "C" { #include "../runtime/RuntimeInterface.h" #include "../runtime/tests/TestTools.h" #include "../runtime/Var.h" #include #include #include +extern "C" { #include } +} +} #include "../bridge/Exceptions.h" diff --git a/backend-v2/tests/codegen/AsyncStackTrace_test.cpp b/backend-v2/tests/codegen/AsyncStackTrace_test.cpp index 9bfb86c7..6a9ab10f 100644 --- a/backend-v2/tests/codegen/AsyncStackTrace_test.cpp +++ b/backend-v2/tests/codegen/AsyncStackTrace_test.cpp @@ -3,14 +3,14 @@ #include "../../jit/JITEngine.h" #include "../../state/ThreadsafeCompilerState.h" #include "bytecode.pb.h" -#include -#include -#include -#include #include #include extern "C" { +#include +#include +#include +#include #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" } diff --git a/backend-v2/tests/codegen/DCE_test.cpp b/backend-v2/tests/codegen/DCE_test.cpp index 117ee01a..51f5a4a9 100644 --- a/backend-v2/tests/codegen/DCE_test.cpp +++ b/backend-v2/tests/codegen/DCE_test.cpp @@ -11,14 +11,16 @@ #include #include -extern "C" { #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" #include #include #include +extern "C" { #include } +} +} using namespace std; using namespace rt; diff --git a/backend-v2/tests/codegen/DeepStackSymbolization_test.cpp b/backend-v2/tests/codegen/DeepStackSymbolization_test.cpp index e7b95b25..81ab4d71 100644 --- a/backend-v2/tests/codegen/DeepStackSymbolization_test.cpp +++ b/backend-v2/tests/codegen/DeepStackSymbolization_test.cpp @@ -7,7 +7,6 @@ #include #include -extern "C" { #include "../../runtime/Class.h" #include "../../runtime/Keyword.h" #include "../../runtime/Var.h" @@ -15,8 +14,11 @@ extern "C" { #include #include #include +extern "C" { #include } +} +} using namespace std; using namespace rt; diff --git a/backend-v2/tests/codegen/DoNode_test.cpp b/backend-v2/tests/codegen/DoNode_test.cpp index b6925be4..a2fe5e25 100644 --- a/backend-v2/tests/codegen/DoNode_test.cpp +++ b/backend-v2/tests/codegen/DoNode_test.cpp @@ -5,20 +5,21 @@ #include "bytecode.pb.h" #include -extern "C" { #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" #include #include #include +extern "C" { #include } +} +} using namespace std; using namespace rt; using namespace clojure::rt::protobuf::bytecode; -extern "C" { RTValue mock_add_bigint(RTValue a, RTValue b) { release(a); release(b); diff --git a/backend-v2/tests/codegen/DoubleAddRepro_test.cpp b/backend-v2/tests/codegen/DoubleAddRepro_test.cpp index c49cd97d..9deee5c1 100644 --- a/backend-v2/tests/codegen/DoubleAddRepro_test.cpp +++ b/backend-v2/tests/codegen/DoubleAddRepro_test.cpp @@ -8,14 +8,16 @@ #include #include -extern "C" { #include "../../runtime/BigInteger.h" #include "../../runtime/String.h" #include "../../runtime/tests/TestTools.h" #include #include #include +extern "C" { #include +} +} void delete_class_description(void *ptr); } diff --git a/backend-v2/tests/codegen/IfNode_test.cpp b/backend-v2/tests/codegen/IfNode_test.cpp index ecc62c74..08336e67 100644 --- a/backend-v2/tests/codegen/IfNode_test.cpp +++ b/backend-v2/tests/codegen/IfNode_test.cpp @@ -7,14 +7,16 @@ #include #include -extern "C" { #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" #include #include #include +extern "C" { #include } +} +} #include "../../bridge/Exceptions.h" @@ -22,7 +24,6 @@ using namespace std; using namespace rt; using namespace clojure::rt::protobuf::bytecode; -extern "C" { RTValue mock_add_bigint(RTValue a, RTValue b) { release(a); release(b); diff --git a/backend-v2/tests/codegen/InstanceCallIC_test.cpp b/backend-v2/tests/codegen/InstanceCallIC_test.cpp index 8fa4f377..fb9a77a2 100644 --- a/backend-v2/tests/codegen/InstanceCallIC_test.cpp +++ b/backend-v2/tests/codegen/InstanceCallIC_test.cpp @@ -7,7 +7,6 @@ #include #include -extern "C" { #include "../../runtime/Keyword.h" #include "../../runtime/PersistentList.h" #include "../../runtime/PersistentVector.h" @@ -17,8 +16,11 @@ extern "C" { #include #include #include +extern "C" { #include } +} +} #include #include @@ -30,7 +32,6 @@ using namespace rt; using namespace clojure::rt::protobuf::bytecode; // Mock implementations for instance methods -extern "C" { int32_t mock_TypeA_foo(PersistentVector *instance, int32_t x) { Ptr_release(instance); return 1000 + x; diff --git a/backend-v2/tests/codegen/InstanceCallNode_test.cpp b/backend-v2/tests/codegen/InstanceCallNode_test.cpp index 4d213026..4b551287 100644 --- a/backend-v2/tests/codegen/InstanceCallNode_test.cpp +++ b/backend-v2/tests/codegen/InstanceCallNode_test.cpp @@ -7,20 +7,20 @@ #include #include -extern "C" { #include "../../runtime/tests/TestTools.h" #include #include #include +extern "C" { #include } +} using namespace std; using namespace rt; using namespace clojure::rt::protobuf::bytecode; // Mock implementation for instance call -extern "C" { int32_t mock_instance_get_value(RTValue instance, int32_t offset) { // We can't easily check 'instance' here without a full class setup, // but we can verify the argument passing and that it was called. diff --git a/backend-v2/tests/codegen/JITArithmetic_test.cpp b/backend-v2/tests/codegen/JITArithmetic_test.cpp index 3f9258c7..bc59e6b3 100644 --- a/backend-v2/tests/codegen/JITArithmetic_test.cpp +++ b/backend-v2/tests/codegen/JITArithmetic_test.cpp @@ -6,14 +6,16 @@ #include "bytecode.pb.h" #include -extern "C" { #include "../../runtime/tests/TestTools.h" #include "../../runtime/BigInteger.h" #include "../../runtime/String.h" #include #include #include +extern "C" { #include +} +} void delete_class_description(void *ptr); } diff --git a/backend-v2/tests/codegen/LetNode_test.cpp b/backend-v2/tests/codegen/LetNode_test.cpp index aa66782b..e46723ae 100644 --- a/backend-v2/tests/codegen/LetNode_test.cpp +++ b/backend-v2/tests/codegen/LetNode_test.cpp @@ -5,20 +5,21 @@ #include "bytecode.pb.h" #include -extern "C" { #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" #include #include #include +extern "C" { #include } +} +} using namespace std; using namespace rt; using namespace clojure::rt::protobuf::bytecode; -extern "C" { RTValue mock_add_bigint(RTValue a, RTValue b) { // We don't really care about the result value, just that it's a BigInt // that needs to be released. diff --git a/backend-v2/tests/codegen/MathIntrinsicsRepro_test.cpp b/backend-v2/tests/codegen/MathIntrinsicsRepro_test.cpp index d9ae19f3..c21c409f 100644 --- a/backend-v2/tests/codegen/MathIntrinsicsRepro_test.cpp +++ b/backend-v2/tests/codegen/MathIntrinsicsRepro_test.cpp @@ -7,13 +7,14 @@ #include #include -extern "C" { #include "../../runtime/tests/TestTools.h" #include #include #include +extern "C" { #include } +} using namespace std; using namespace rt; diff --git a/backend-v2/tests/codegen/MathIntrinsics_test.cpp b/backend-v2/tests/codegen/MathIntrinsics_test.cpp index 59b99263..a8b1f2e2 100644 --- a/backend-v2/tests/codegen/MathIntrinsics_test.cpp +++ b/backend-v2/tests/codegen/MathIntrinsics_test.cpp @@ -9,14 +9,16 @@ #include #include -extern "C" { #include "../../runtime/BigInteger.h" #include "../../runtime/String.h" #include "../../runtime/tests/TestTools.h" #include #include #include +extern "C" { #include +} +} void delete_class_description(void *ptr); } diff --git a/backend-v2/tests/codegen/NoThrow_test.cpp b/backend-v2/tests/codegen/NoThrow_test.cpp index cf8c73ab..c7228912 100644 --- a/backend-v2/tests/codegen/NoThrow_test.cpp +++ b/backend-v2/tests/codegen/NoThrow_test.cpp @@ -5,14 +5,16 @@ #include "bytecode.pb.h" #include -extern "C" { #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" #include #include #include +extern "C" { #include } +} +} using namespace std; using namespace rt; diff --git a/backend-v2/tests/codegen/O3_DebugInfo_test.cpp b/backend-v2/tests/codegen/O3_DebugInfo_test.cpp index 8a0c2a10..a2893dd7 100644 --- a/backend-v2/tests/codegen/O3_DebugInfo_test.cpp +++ b/backend-v2/tests/codegen/O3_DebugInfo_test.cpp @@ -7,14 +7,16 @@ #include #include -extern "C" { #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" #include #include #include +extern "C" { #include } +} +} #include "../../bridge/Exceptions.h" diff --git a/backend-v2/tests/codegen/Overflow_test.cpp b/backend-v2/tests/codegen/Overflow_test.cpp index 167d39e4..d79d5250 100644 --- a/backend-v2/tests/codegen/Overflow_test.cpp +++ b/backend-v2/tests/codegen/Overflow_test.cpp @@ -7,14 +7,16 @@ #include #include -extern "C" { #include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" #include #include #include +extern "C" { #include } +} +} #include "../../bridge/Exceptions.h" diff --git a/backend-v2/tests/codegen/StaticCallNode_test.cpp b/backend-v2/tests/codegen/StaticCallNode_test.cpp index 968deea8..ab043891 100644 --- a/backend-v2/tests/codegen/StaticCallNode_test.cpp +++ b/backend-v2/tests/codegen/StaticCallNode_test.cpp @@ -7,20 +7,20 @@ #include #include -extern "C" { #include "../../runtime/tests/TestTools.h" #include #include #include +extern "C" { #include } +} using namespace std; using namespace rt; using namespace clojure::rt::protobuf::bytecode; // Mock implementations for dispatch to call -extern "C" { int32_t mock_add_int(int32_t a, int32_t b) { return a + b + 1000; // Offset to verify this was called } diff --git a/backend-v2/tests/codegen/StaticCallType_test.cpp b/backend-v2/tests/codegen/StaticCallType_test.cpp index 13a5daa0..bc2dd2e5 100644 --- a/backend-v2/tests/codegen/StaticCallType_test.cpp +++ b/backend-v2/tests/codegen/StaticCallType_test.cpp @@ -14,7 +14,6 @@ #include "../../runtime/Object.h" #include "../../runtime/RuntimeInterface.h" -extern "C" { // Minimal declarations for functions provided by runtime_helpers.c void test_initialise_memory(); void test_RuntimeInterface_cleanup(); @@ -27,8 +26,11 @@ void test_release(RTValue v); #include #include #include +extern "C" { #include } +} +} using namespace std; using namespace rt; diff --git a/backend-v2/tests/codegen/StaticFieldNode_test.cpp b/backend-v2/tests/codegen/StaticFieldNode_test.cpp index 9326e0d4..c28ce32e 100644 --- a/backend-v2/tests/codegen/StaticFieldNode_test.cpp +++ b/backend-v2/tests/codegen/StaticFieldNode_test.cpp @@ -7,13 +7,14 @@ #include "bytecode.pb.h" #include -extern "C" { #include "../../runtime/tests/TestTools.h" #include #include #include +extern "C" { #include } +} using namespace std; using namespace rt; diff --git a/backend-v2/tests/codegen/VoidInvokeRepro_test.cpp b/backend-v2/tests/codegen/VoidInvokeRepro_test.cpp index 259d2722..9423d8cd 100644 --- a/backend-v2/tests/codegen/VoidInvokeRepro_test.cpp +++ b/backend-v2/tests/codegen/VoidInvokeRepro_test.cpp @@ -4,13 +4,14 @@ #include "bytecode.pb.h" #include -extern "C" { #include "../../runtime/tests/TestTools.h" #include #include #include +extern "C" { #include } +} using namespace rt; using namespace clojure::rt::protobuf::bytecode; diff --git a/backend-v2/tests/tools/EdnParser_test.cpp b/backend-v2/tests/tools/EdnParser_test.cpp index a785f41b..433073f8 100644 --- a/backend-v2/tests/tools/EdnParser_test.cpp +++ b/backend-v2/tests/tools/EdnParser_test.cpp @@ -8,7 +8,6 @@ #include #include -extern "C" { #include "../../runtime/Keyword.h" #include "../../runtime/PersistentArrayMap.h" #include "../../runtime/PersistentVector.h" @@ -17,6 +16,7 @@ extern "C" { #include "../../runtime/Symbol.h" #include "../../runtime/tests/TestTools.h" #include +} #include #include #include From 72c7431cebe80a1833e39782f8163db2197fafcf Mon Sep 17 00:00:00 2001 From: Marek Lipert Date: Tue, 24 Mar 2026 08:08:11 +0100 Subject: [PATCH 10/10] Includes --- backend-v2/tests/JITSafety_test.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend-v2/tests/JITSafety_test.cpp b/backend-v2/tests/JITSafety_test.cpp index 74c4d870..39bdbf35 100644 --- a/backend-v2/tests/JITSafety_test.cpp +++ b/backend-v2/tests/JITSafety_test.cpp @@ -6,17 +6,15 @@ #include #include +extern "C" { #include "../runtime/RuntimeInterface.h" #include "../runtime/tests/TestTools.h" #include "../bridge/InstanceCallStub.h" #include #include #include -extern "C" { #include } -} -} using namespace rt;