Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: C++ CI Linux

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

jobs:
build-and-test:
runs-on: ubuntu-latest

defaults:
run:
working-directory: ./backend-v2

steps:
- uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake gcc g++ libprotobuf-dev protobuf-compiler libcmocka-dev libgmp-dev lsb-release wget software-properties-common gnupg zlib1g-dev

- name: Install LLVM 20
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20
sudo apt-get install -y clang-20 lld-20 libc++-20-dev libc++abi-20-dev llvm-20-dev

- name: Configure CMake
run: |
cmake -S . -B build \
-DCMAKE_C_COMPILER=clang-20 \
-DCMAKE_CXX_COMPILER=clang++-20 \
-DLLVM_DIR=/usr/lib/llvm-20/lib/cmake/llvm \
-DCMAKE_BUILD_TYPE=Debug

- name: Build
run: cmake --build build -j$(nproc)

- name: Run Tests
working-directory: ./backend-v2/build
run: ctest --output-on-failure
76 changes: 68 additions & 8 deletions backend-v2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ message(STATUS "CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}")
message(STATUS "CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}")
message(STATUS "CMAKE_HOST_SYSTEM_PROCESSOR: ${CMAKE_HOST_SYSTEM_PROCESSOR}")

set(CMAKE_OSX_ARCHITECTURES "arm64")
#set(CMAKE_OPTIMIZE_DEPENDENCIES TRUE)

if(NOT CMAKE_BUILD_TYPE)
Expand All @@ -21,14 +20,51 @@ endif()
set(CMAKE_CXX_FLAGS_DEBUG "-g -fsanitize=address -fno-omit-frame-pointer -Wall -Wextra -Wstrict-aliasing -Wno-unused-parameter -fstandalone-debug")
set(CMAKE_CXX_FLAGS_RELEASE "-Ofast")
set(CMAKE_CXX_STANDARD 20)
add_compile_definitions(BUILD_TYPE="${CMAKE_BUILD_TYPE}")

set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf-with-dsym")
if(APPLE)
if(NOT CMAKE_OSX_ARCHITECTURES)
set(CMAKE_OSX_ARCHITECTURES "arm64")
endif()
set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf-with-dsym")
endif()

add_subdirectory(runtime)

# Use LLVM's new approach for finding packages.
# LLVM 20.1 is very new, ensure your system has LLVMConfig.cmake for it.
find_package(LLVM 20.1 CONFIG REQUIRED) # Added REQUIRED for stricter checking.
# --- Dependency Discovery ---

# Helper function to find Homebrew prefix on macOS
macro(find_homebrew_prefix PREFIX_VAR FORMULA_NAME)
if(APPLE AND NOT ${PREFIX_VAR})
execute_process(
COMMAND brew --prefix ${FORMULA_NAME}
RESULT_VARIABLE BREW_RESULT
OUTPUT_VARIABLE BREW_PREFIX
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(BREW_RESULT EQUAL 0)
set(${PREFIX_VAR} "${BREW_PREFIX}")
endif()
endif()
endmacro()

# 1. LLVM Detection
# We prefer LLVM 20.x for API stability (getContext() availability).
find_package(LLVM 20.1 CONFIG QUIET)

# Fallback for macOS Homebrew if not found in standard paths
if(NOT LLVM_FOUND AND APPLE)
find_homebrew_prefix(LLVM_PREFIX llvm@20)
if(LLVM_PREFIX)
set(LLVM_DIR "${LLVM_PREFIX}/lib/cmake/llvm")
find_package(LLVM 20.1 CONFIG REQUIRED)
endif()
endif()

# Final check for LLVM
if(NOT LLVM_FOUND)
find_package(LLVM REQUIRED CONFIG)
endif()

message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
Expand Down Expand Up @@ -65,6 +101,7 @@ message(STATUS "LLVM Libraries to link: ${LLVM_LIBS}")
include_directories(${LLVM_INCLUDE_DIRS})
separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS})
add_definitions(${LLVM_DEFINITIONS_LIST})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})

# --- END OF CRITICAL CHANGES FOR LINKING ---

Expand All @@ -87,17 +124,40 @@ set(BACKEND_SOURCES
codegen/ops/MapNode.cpp)


find_package(Protobuf REQUIRED)
# 2. Protobuf Detection
find_package(Protobuf CONFIG QUIET)
if(NOT Protobuf_FOUND)
find_package(Protobuf REQUIRED)
endif()

include_directories(${Protobuf_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ../model/bytecode.proto)

# Use the modern protobuf_generate command if available, fallback to protobuf_generate_cpp
if(COMMAND protobuf_generate)
protobuf_generate(LANGUAGE cpp OUT_VAR PROTO_SRCS PROTOC_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}" APPEND_PATH PROTOS ../model/bytecode.proto)
else()
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ../model/bytecode.proto)
endif()

add_library(backend_lib STATIC ${BACKEND_SOURCES} ${PROTO_SRCS} ${PROTO_HDRS})
target_link_libraries(backend_lib PUBLIC runtime ${LLVM_LIBS} ${Protobuf_LIBRARIES} ${GMP_LIBRARY})
# Use target-based linking for Protobuf to handle Abseil dependencies automatically
if(TARGET protobuf::libprotobuf)
set(PROTOBUF_LINK_TARGET protobuf::libprotobuf)
else()
set(PROTOBUF_LINK_TARGET ${Protobuf_LIBRARIES})
endif()

target_link_libraries(backend_lib PUBLIC runtime ${LLVM_LIBS} ${PROTOBUF_LINK_TARGET} ${GMP_LIBRARY})

add_executable(clojure-rt main.cpp)
target_link_libraries(clojure-rt PUBLIC backend_lib)

# On Linux, export symbols so the JIT can find runtime functions
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_options(clojure-rt PRIVATE -rdynamic)
endif()

find_path(GMP_INCLUDE_DIRS NAMES gmp.h)
find_library(GMP_LIBRARY NAMES gmp libgmp)
include(FindPackageHandleStandardArgs)
Expand Down
55 changes: 54 additions & 1 deletion backend-v2/bridge/Exceptions.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
#include "Exceptions.h"
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <stdio.h>
#include <sys/_types/_intptr_t.h>

#ifdef __APPLE__
#include <mach-o/dyld.h>
#elif defined(__linux__)
#include <limits.h>
#include <unistd.h>
#endif

namespace rt {

Expand All @@ -18,6 +26,11 @@ LanguageException::LanguageException(const std::string &name, RTValue message,
}
}

LanguageException::~LanguageException() noexcept {
release(message);
release(payload);
}

void LanguageException::printRawTrace() const {
for (uword_t addr : stackAddresses) {
printf(" [JIT ADDR] %p\n", (void *)addr);
Expand Down Expand Up @@ -83,6 +96,46 @@ LanguageException::toString(llvm::symbolize::LLVMSymbolizer &symbolizer,

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);
}
#endif
return "";
}

std::string getExceptionString(const LanguageException &e) {
llvm::symbolize::LLVMSymbolizer::Options options;
options.Demangle = true;
options.PrintFunctions = llvm::symbolize::FunctionNameKind::LinkageName;
llvm::symbolize::LLVMSymbolizer symbolizer(options);

std::string exePath = getSelfExecutablePath();
std::string moduleName = exePath;

#ifdef __APPLE__
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;
intptr_t slide = _dyld_get_image_vmaddr_slide(0);
#else
intptr_t slide = 0;
#endif

return e.toString(symbolizer, moduleName, slide);
}

} // namespace rt

extern "C" {
Expand Down
29 changes: 16 additions & 13 deletions backend-v2/bridge/Exceptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,45 @@
#define RT_EXCEPTIONS

#include "bytecode.pb.h"
#include <sstream>
#include "llvm/DebugInfo/Symbolize/Symbolize.h"
#include "llvm/Support/Error.h"
#include <sstream>


#include <gmp.h>
#include "../RuntimeHeaders.h"
#include <gmp.h>

#include <execinfo.h>
#include <vector>
#include <string>
#include <exception>
#include <execinfo.h>
#include <string>
#include <vector>

using namespace clojure::rt::protobuf::bytecode;

namespace rt {

class LanguageException : public std::exception {
std::string name;
RTValue message;
RTValue message;
RTValue payload;
std::vector<uword_t> stackAddresses;
public:
std::vector<uword_t> stackAddresses;

public:
LanguageException(const std::string &name, RTValue message, RTValue payload);
~LanguageException() noexcept override;
void printRawTrace() const;
std::string toString(llvm::symbolize::LLVMSymbolizer &symbolizer,
const std::string &moduleName = "JITMemoryBuffer",
const intptr_t slide = 0x0) const;
};

}
std::string getExceptionString(const LanguageException &e);

} // namespace rt

extern "C" {
void throwInternalInconsistencyException(const std::string &errorMessage);
void throwCodeGenerationException(const std::string &errorMessage, const Node &node);
void throwInternalInconsistencyException(const std::string &errorMessage);
void throwCodeGenerationException(const std::string &errorMessage,
const Node &node);
}


#endif
66 changes: 35 additions & 31 deletions backend-v2/cljassert.h
Original file line number Diff line number Diff line change
@@ -1,49 +1,53 @@
#ifndef CLJASSERT_H
#define CLJASSERT_H

#include <cstdlib> // for free()
#include <execinfo.h> // for backtrace() and backtrace_symbols()
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <sstream>
#include <iostream>
#include <execinfo.h> // for backtrace() and backtrace_symbols()
#include <cstdlib> // for free()

inline std::string getStackTrace(unsigned int maxFrames = 200) {
void* addrlist[maxFrames+1];
// Avoid VLAs (Clang extension in C++) by using a fixed-size buffer.
// 256 frames is more than enough for diagnostic traces.
static const int kMaxFrames = 256;
void *addrlist[kMaxFrames];
unsigned int actualMax = (maxFrames < kMaxFrames) ? maxFrames : kMaxFrames;

// retrieve current stack addresses
int addrlen = backtrace(addrlist, (int)(sizeof(addrlist)/sizeof(void*)));
// retrieve current stack addresses
int addrlen = backtrace(addrlist, actualMax);

if (addrlen == 0) {
return " <empty, no stack trace>\n";
}
if (addrlen == 0) {
return " <empty, no stack trace>\n";
}

// Convert addresses into an array of strings (mangled symbols)
char** symbollist = backtrace_symbols(addrlist, addrlen);
// Convert addresses into an array of strings (mangled symbols)
char **symbollist = backtrace_symbols(addrlist, addrlen);

if (!symbollist) {
return " <no symbols available>\n";
}
if (!symbollist) {
return " <no symbols available>\n";
}

std::ostringstream oss;
for (int i = 0; i < addrlen; i++) {
oss << " " << symbollist[i] << "\n";
}
std::ostringstream oss;
for (int i = 0; i < addrlen; i++) {
oss << " " << symbollist[i] << "\n";
}

free(symbollist);
return oss.str();
free(symbollist);
return oss.str();
}

#define CLJ_ASSERT(cond, msg) \
do { \
if (!(cond)) { \
std::ostringstream oss; \
oss << "Assertion failed: (" #cond ") " << msg \
<< "\nFile: " << __FILE__ << "\nLine: " << __LINE__ << "\n" \
<< "Stack trace:\n" << getStackTrace(); \
throw std::runtime_error(oss.str()); \
} \
} while (false)

do { \
if (!(cond)) { \
std::ostringstream oss; \
oss << "Assertion failed: (" #cond ") " << msg << "\nFile: " << __FILE__ \
<< "\nLine: " << __LINE__ << "\n" \
<< "Stack trace:\n" \
<< getStackTrace(); \
throw std::runtime_error(oss.str()); \
} \
} while (false)

#endif
2 changes: 1 addition & 1 deletion backend-v2/codegen/CodeGen.h
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#ifndef CODEGEN_H
#define CODEGEN_H

#include "../bytecode.pb.h"
#include "../state/ThreadsafeCompilerState.h"
#include "DynamicConstructor.h"
#include "LLVMTypes.h"
#include "MemoryManagement.h"
#include "TypedValue.h"
#include "ValueEncoder.h"
#include "VariableBindings.h"
#include "bytecode.pb.h"
#include "invoke/InvokeManager.h"
#include <llvm/ExecutionEngine/Orc/ThreadSafeModule.h>
#include <llvm/IR/DerivedTypes.h>
Expand Down
Loading