diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..3c32f915 --- /dev/null +++ b/.github/workflows/ci.yml @@ -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 diff --git a/backend-v2/CMakeLists.txt b/backend-v2/CMakeLists.txt index ed149111..3a42f0cb 100644 --- a/backend-v2/CMakeLists.txt +++ b/backend-v2/CMakeLists.txt @@ -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) @@ -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}") @@ -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 --- @@ -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) diff --git a/backend-v2/bridge/Exceptions.cpp b/backend-v2/bridge/Exceptions.cpp index 2d1ea10e..04998b0c 100644 --- a/backend-v2/bridge/Exceptions.cpp +++ b/backend-v2/bridge/Exceptions.cpp @@ -1,7 +1,15 @@ #include "Exceptions.h" +#include #include +#include #include -#include + +#ifdef __APPLE__ +#include +#elif defined(__linux__) +#include +#include +#endif namespace rt { @@ -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); @@ -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" { diff --git a/backend-v2/bridge/Exceptions.h b/backend-v2/bridge/Exceptions.h index d433702f..40307403 100644 --- a/backend-v2/bridge/Exceptions.h +++ b/backend-v2/bridge/Exceptions.h @@ -2,18 +2,17 @@ #define RT_EXCEPTIONS #include "bytecode.pb.h" -#include #include "llvm/DebugInfo/Symbolize/Symbolize.h" #include "llvm/Support/Error.h" +#include - -#include #include "../RuntimeHeaders.h" +#include -#include -#include -#include #include +#include +#include +#include using namespace clojure::rt::protobuf::bytecode; @@ -21,23 +20,27 @@ namespace rt { class LanguageException : public std::exception { std::string name; - RTValue message; + RTValue message; RTValue payload; - std::vector stackAddresses; -public: + std::vector 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 diff --git a/backend-v2/cljassert.h b/backend-v2/cljassert.h index 0f774996..5769c5fd 100644 --- a/backend-v2/cljassert.h +++ b/backend-v2/cljassert.h @@ -1,49 +1,53 @@ #ifndef CLJASSERT_H #define CLJASSERT_H +#include // for free() +#include // for backtrace() and backtrace_symbols() +#include +#include #include #include -#include -#include -#include // for backtrace() and backtrace_symbols() -#include // 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 " \n"; - } + if (addrlen == 0) { + return " \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 " \n"; - } + if (!symbollist) { + return " \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 diff --git a/backend-v2/codegen/CodeGen.h b/backend-v2/codegen/CodeGen.h index 1c927cfb..8eb00c3a 100644 --- a/backend-v2/codegen/CodeGen.h +++ b/backend-v2/codegen/CodeGen.h @@ -1,7 +1,6 @@ #ifndef CODEGEN_H #define CODEGEN_H -#include "../bytecode.pb.h" #include "../state/ThreadsafeCompilerState.h" #include "DynamicConstructor.h" #include "LLVMTypes.h" @@ -9,6 +8,7 @@ #include "TypedValue.h" #include "ValueEncoder.h" #include "VariableBindings.h" +#include "bytecode.pb.h" #include "invoke/InvokeManager.h" #include #include diff --git a/backend-v2/codegen/DynamicConstructor.cpp b/backend-v2/codegen/DynamicConstructor.cpp index fd71470a..7ecb5728 100644 --- a/backend-v2/codegen/DynamicConstructor.cpp +++ b/backend-v2/codegen/DynamicConstructor.cpp @@ -8,9 +8,9 @@ #include "ValueEncoder.h" #include "invoke/InvokeManager.h" #include "llvm/ADT/StringSwitch.h" +#include #include #include -#include using namespace llvm; diff --git a/backend-v2/codegen/MemoryManagement.cpp b/backend-v2/codegen/MemoryManagement.cpp index 1e5176dd..875c3421 100644 --- a/backend-v2/codegen/MemoryManagement.cpp +++ b/backend-v2/codegen/MemoryManagement.cpp @@ -1,138 +1,151 @@ #include "MemoryManagement.h" -#include -#include -#include -#include "../runtime/word.h" +#include "../RuntimeHeaders.h" +#include "../bridge/Exceptions.h" #include "../cljassert.h" +#include "../runtime/word.h" #include "DynamicConstructor.h" #include "LLVMTypes.h" #include "TypedValue.h" #include "ValueEncoder.h" -#include "llvm/ADT/StringSwitch.h" -#include "../RuntimeHeaders.h" -#include "../bridge/Exceptions.h" #include "VariableBindings.h" +#include "llvm/ADT/StringSwitch.h" +#include +#include +#include using namespace llvm; namespace rt { - MemoryManagement::MemoryManagement(llvm::LLVMContext &c, IRBuilder<> &b, - ValueEncoder &v, LLVMTypes &t, - VariableBindings &vb, - InvokeManager &i, DynamicConstructor &d) +MemoryManagement::MemoryManagement(llvm::LLVMContext &c, IRBuilder<> &b, + ValueEncoder &v, LLVMTypes &t, + VariableBindings &vb, + InvokeManager &i, DynamicConstructor &d) : context(c), builder(b), valueEncoder(v), types(t), variableBindingStack(vb), invoke(i), dynamicConstructor(d) {} - - /* Applies memory operations (retains/releases) according to the guidance generated by the frontend. - In the future, ( TODO ) a single retain/release call could be made with a counter (if appropriate runtime modification is done) */ - - void MemoryManagement::dynamicMemoryGuidance(const MemoryManagementGuidance &guidance) { - auto name = guidance.variablename(); - auto change = guidance.requiredrefcountchange(); - - for (word_t depth = variableBindingStack.stackDepth() - 1; depth >= 0; - depth--) { - auto val = variableBindingStack.find(name, depth); - if (val) { - while(change != 0) { - if(change > 0) { dynamicRetain(*val); change--; } - else { dynamicRelease(*val); change++; } + +/* Applies memory operations (retains/releases) according to the guidance + generated by the frontend. In the future, ( TODO ) a single retain/release + call could be made with a counter (if appropriate runtime modification is + done) */ + +void MemoryManagement::dynamicMemoryGuidance( + const MemoryManagementGuidance &guidance) { + auto name = guidance.variablename(); + auto change = guidance.requiredrefcountchange(); + + for (word_t depth = variableBindingStack.stackDepth() - 1; depth >= 0; + depth--) { + auto val = variableBindingStack.find(name, depth); + if (val) { + while (change != 0) { + if (change > 0) { + dynamicRetain(*val); + change--; + } else { + dynamicRelease(*val); + change++; } - break; } + break; } } +} - void MemoryManagement::dynamicRetain(TypedValue &target) { - if (target.type.isScalar() || target.type.isBoxedScalar()) - return; +void MemoryManagement::dynamicRetain(TypedValue &target) { + if (target.type.isScalar() || target.type.isBoxedScalar()) + return; - Metadata * metaPtr = dyn_cast(MDString::get(context, "retain")); - MDNode * meta = MDNode::get(context, metaPtr); + Metadata *metaPtr = dyn_cast(MDString::get(context, "retain")); + MDNode *meta = MDNode::get(context, metaPtr); #ifndef REFCOUNT_TRACING - if (target.type.isDetermined()) { - auto prepare = - target.type.isBoxedPointer() ? valueEncoder.unbox(target) : target; - Value *gepPtr = builder.CreateStructGEP(types.RT_objectTy, prepare.value, 0, "get_refcount"); - auto rmw = builder.CreateAtomicRMW(AtomicRMWInst::BinOp::Add, gepPtr, - // TODO - 32/64 bit alignment - builder.getInt32(1), MaybeAlign(), - AtomicOrdering::Monotonic); - if (auto *i = dyn_cast(rmw)) { - i->setMetadata("memory_management", meta); - } - return; - } + if (target.type.isDetermined()) { + auto prepare = + target.type.isBoxedPointer() ? valueEncoder.unbox(target) : target; + Value *gepPtr = builder.CreateStructGEP(types.RT_objectTy, prepare.value, 0, + "get_refcount"); + auto rmw = builder.CreateAtomicRMW(AtomicRMWInst::BinOp::Add, gepPtr, + // TODO - 32/64 bit alignment + builder.getInt32(1), MaybeAlign(), + AtomicOrdering::Monotonic); + if (auto *i = dyn_cast(rmw)) { + i->setMetadata("memory_management", meta); + } + return; + } #endif - - TypedValue retain = invoke.invokeRuntime("retain", nullptr, {ObjectTypeSet::all()}, {valueEncoder.box(target)}); - if (auto *i = dyn_cast(retain.value)) { - i->setMetadata("memory_management", meta); - } + + TypedValue retain = invoke.invokeRuntime( + "retain", nullptr, {ObjectTypeSet::all()}, {valueEncoder.box(target)}); + if (auto *i = dyn_cast(retain.value)) { + i->setMetadata("memory_management", meta); } - - TypedValue MemoryManagement::dynamicRelease(TypedValue &target) { - if (target.type.isScalar() || target.type.isBoxedScalar()) - return dynamicConstructor.createBoolean(false); - - Metadata * metaPtr = dyn_cast(MDString::get(context, "release")); - MDNode * meta = MDNode::get(context, metaPtr); - +} + +TypedValue MemoryManagement::dynamicRelease(TypedValue &target) { + if (target.type.isScalar() || target.type.isBoxedScalar()) + return dynamicConstructor.createBoolean(false); + + Metadata *metaPtr = dyn_cast(MDString::get(context, "release")); + MDNode *meta = MDNode::get(context, metaPtr); + #ifndef REFCOUNT_TRACKING - if (target.type.isDetermined()) { - auto prepare = - target.type.isBoxedPointer() ? valueEncoder.unbox(target) : target; - Value *gepPtr = builder.CreateStructGEP(types.RT_objectTy, prepare.value, 0, "get_refcount"); - auto rmw = builder.CreateAtomicRMW(AtomicRMWInst::BinOp::Sub, gepPtr, - // TODO - 32/64 bit alignment - builder.getInt32(1), MaybeAlign(), - AtomicOrdering::Release); - if (auto *i = dyn_cast(rmw)) { - i->setMetadata("memory_management", meta); - } - - Value *condValue = builder.CreateICmpEQ(rmw, dynamicConstructor.createBoolean(true).value, "cmp_jj"); - Function *parentFunction = builder.GetInsertBlock()->getParent(); - BasicBlock *destroyBB = llvm::BasicBlock::Create(context, "destroy", parentFunction); - BasicBlock *ignoreBB = llvm::BasicBlock::Create(context, "ignore"); - BasicBlock *mergeBB = llvm::BasicBlock::Create(context, "release_cont"); - - builder.CreateCondBr(condValue, destroyBB, ignoreBB); - builder.SetInsertPoint(destroyBB); - builder.CreateFence(llvm::AtomicOrdering::Acquire, - llvm::SyncScope::System); - - auto retValType = ObjectTypeSet(booleanType); - invoke.invokeRuntime("Object_destroy", nullptr, - {ObjectTypeSet::all(), ObjectTypeSet(booleanType)}, - {prepare, dynamicConstructor.createBoolean(true)}); - - builder.CreateBr(mergeBB); - - parentFunction->insert(parentFunction->end(), ignoreBB); - builder.SetInsertPoint(ignoreBB); - builder.CreateBr(mergeBB); - - parentFunction->insert(parentFunction->end(), mergeBB); - builder.SetInsertPoint(mergeBB); - - llvm::PHINode *phiNode = builder.CreatePHI(types.i1Ty, 2, "release_phi"); - phiNode->addIncoming(dynamicConstructor.createBoolean(true).value, destroyBB); - phiNode->addIncoming(dynamicConstructor.createBoolean(false).value, ignoreBB); - return TypedValue(ObjectTypeSet(booleanType), phiNode); - } -#endif - - TypedValue release = invoke.invokeRuntime("release", nullptr, {ObjectTypeSet::all()}, {valueEncoder.box(target)}); - if (auto *i = dyn_cast(release.value)) { - i->setMetadata("memory_management", meta); - } - return release; + if (target.type.isDetermined()) { + auto prepare = + target.type.isBoxedPointer() ? valueEncoder.unbox(target) : target; + Value *gepPtr = builder.CreateStructGEP(types.RT_objectTy, prepare.value, 0, + "get_refcount"); + auto rmw = builder.CreateAtomicRMW(AtomicRMWInst::BinOp::Sub, gepPtr, + // TODO - 32/64 bit alignment + builder.getInt32(1), MaybeAlign(), + AtomicOrdering::Release); + if (auto *i = dyn_cast(rmw)) { + i->setMetadata("memory_management", meta); + } + + Value *condValue = builder.CreateICmpEQ( + rmw, dynamicConstructor.createBoolean(true).value, "cmp_jj"); + Function *parentFunction = builder.GetInsertBlock()->getParent(); + BasicBlock *destroyBB = + llvm::BasicBlock::Create(context, "destroy", parentFunction); + BasicBlock *ignoreBB = llvm::BasicBlock::Create(context, "ignore"); + BasicBlock *mergeBB = llvm::BasicBlock::Create(context, "release_cont"); + + builder.CreateCondBr(condValue, destroyBB, ignoreBB); + builder.SetInsertPoint(destroyBB); + builder.CreateFence(llvm::AtomicOrdering::Acquire, llvm::SyncScope::System); + + auto retValType = ObjectTypeSet(booleanType); + invoke.invokeRuntime("Object_destroy", nullptr, + {ObjectTypeSet::all(), ObjectTypeSet(booleanType)}, + {prepare, dynamicConstructor.createBoolean(true)}); + + builder.CreateBr(mergeBB); + + parentFunction->insert(parentFunction->end(), ignoreBB); + builder.SetInsertPoint(ignoreBB); + builder.CreateBr(mergeBB); + + parentFunction->insert(parentFunction->end(), mergeBB); + builder.SetInsertPoint(mergeBB); + + llvm::PHINode *phiNode = builder.CreatePHI(types.i1Ty, 2, "release_phi"); + phiNode->addIncoming(dynamicConstructor.createBoolean(true).value, + destroyBB); + phiNode->addIncoming(dynamicConstructor.createBoolean(false).value, + ignoreBB); + return TypedValue(ObjectTypeSet(booleanType), phiNode); } - void MemoryManagement::dynamicIsReusable(TypedValue &target) { +#endif - } + TypedValue release = invoke.invokeRuntime( + "release", nullptr, {ObjectTypeSet::all()}, {valueEncoder.box(target)}); + if (auto *i = dyn_cast(release.value)) { + i->setMetadata("memory_management", meta); + } + return release; +} +void MemoryManagement::dynamicIsReusable(TypedValue &target) {} } // namespace rt diff --git a/backend-v2/codegen/invoke/InvokeManager.cpp b/backend-v2/codegen/invoke/InvokeManager.cpp index ed5f7b3b..fbae1ed1 100644 --- a/backend-v2/codegen/invoke/InvokeManager.cpp +++ b/backend-v2/codegen/invoke/InvokeManager.cpp @@ -8,7 +8,8 @@ using namespace std; namespace rt { InvokeManager::InvokeManager(llvm::IRBuilder<> &b, llvm::Module &m, - ValueEncoder &v, LLVMTypes &t, ThreadsafeCompilerState &s) + ValueEncoder &v, LLVMTypes &t, + ThreadsafeCompilerState &s) : builder(b), theModule(m), valueEncoder(v), types(t), state(s) { intrinsics = { // Arithmetic @@ -39,59 +40,60 @@ InvokeManager::InvokeManager(llvm::IRBuilder<> &b, llvm::Module &m, ObjectTypeSet InvokeManager::returnValueType(PersistentArrayMap *description) { RTValue returnValueTypeIndicator = PersistentArrayMap_get( - description, Keyword_create(String_create("returns"))); + description, Keyword_create(String_create("returns"))); - objectType returnValueTypeIndicatorType = getType(returnValueTypeIndicator); - if (returnValueTypeIndicatorType != keywordType && - returnValueTypeIndicatorType != symbolType) { - release(returnValueTypeIndicator); - throwInternalInconsistencyException("Return type must be an alias or a symbol"); - } + objectType returnValueTypeIndicatorType = getType(returnValueTypeIndicator); + if (returnValueTypeIndicatorType != keywordType && + returnValueTypeIndicatorType != symbolType) { + release(returnValueTypeIndicator); + throwInternalInconsistencyException( + "Return type must be an alias or a symbol"); + } - String *compactifiedReturnTypeIndicatorString = - String_compactify(toString(returnValueTypeIndicator)); + String *compactifiedReturnTypeIndicatorString = + String_compactify(toString(returnValueTypeIndicator)); - PersistentArrayMap *returnValueType = state.internalClassRegistry.getCurrent(String_c_str(compactifiedReturnTypeIndicatorString)); - Ptr_release(compactifiedReturnTypeIndicatorString); - - RTValue returnValueTypeEnum = - PersistentArrayMap_get(returnValueType, - Keyword_create(String_create("object-type"))); + PersistentArrayMap *returnValueType = state.internalClassRegistry.getCurrent( + String_c_str(compactifiedReturnTypeIndicatorString)); + Ptr_release(compactifiedReturnTypeIndicatorString); - if (getType(returnValueTypeEnum) != integerType) { - release(returnValueTypeEnum); - throwInternalInconsistencyException("Return value type must be an internal type"); - } + RTValue returnValueTypeEnum = PersistentArrayMap_get( + returnValueType, Keyword_create(String_create("object-type"))); - - ObjectTypeSet returnValueTypeSet((objectType)RT_unboxInt32(returnValueTypeEnum)); - /* Only a formality, integers are not memory managed */ + if (getType(returnValueTypeEnum) != integerType) { release(returnValueTypeEnum); - return returnValueTypeSet; -} + throwInternalInconsistencyException( + "Return value type must be an internal type"); + } + ObjectTypeSet returnValueTypeSet( + (objectType)RT_unboxInt32(returnValueTypeEnum)); + /* Only a formality, integers are not memory managed */ + release(returnValueTypeEnum); + return returnValueTypeSet; +} bool InvokeManager::checkIntrinsicArgs(PersistentArrayMap *description, const std::vector &args) { ThreadsafeRegistry &internalClassRegistry = state.internalClassRegistry; - - RTValue argsVecRaw = - PersistentArrayMap_get(description, Keyword_create(String_create("args"))); + + RTValue argsVecRaw = PersistentArrayMap_get( + description, Keyword_create(String_create("args"))); if (getType(argsVecRaw) != persistentVectorType) { release(argsVecRaw); - throwInternalInconsistencyException("Intrinsic call: :args is not a vector"); + throwInternalInconsistencyException( + "Intrinsic call: :args is not a vector"); } - - PersistentVector *argsVec = - (PersistentVector *)RT_unboxPtr(argsVecRaw); + + PersistentVector *argsVec = (PersistentVector *)RT_unboxPtr(argsVecRaw); if (PersistentVector_count(argsVec) != args.size()) { release(argsVecRaw); return false; } - + PersistentVectorIterator it = PersistentVector_iterator(argsVec); for (size_t i = 0; i < args.size(); i++) { @@ -107,35 +109,37 @@ bool InvokeManager::checkIntrinsicArgs(PersistentArrayMap *description, RTValue anyKeyword = Keyword_create(String_create("any")); if (equals(argRaw, anyKeyword)) { release(anyKeyword); - PersistentVector_iteratorNext(&it); + PersistentVector_iteratorNext(&it); continue; } release(anyKeyword); release(argsVecRaw); return false; - } - + } + PersistentArrayMap *argClass = internalClassRegistry.getCurrent((int32_t)arg.determinedType()); - /* Intrinsics are supported only for built-in objects (represented by ObjectTypeSet) */ + /* Intrinsics are supported only for built-in objects (represented by + * ObjectTypeSet) */ if (!argClass) { release(argsVecRaw); - throwInternalInconsistencyException("Intrinsic call: only basic types can be used in the intrinsics"); + throwInternalInconsistencyException( + "Intrinsic call: only basic types can be used in the intrinsics"); } Ptr_retain(argClass); RTValue className = PersistentArrayMap_get(argClass, Keyword_create(String_create("name"))); - RTValue classAlias = - PersistentArrayMap_get(argClass, Keyword_create(String_create("alias"))); + RTValue classAlias = PersistentArrayMap_get( + argClass, Keyword_create(String_create("alias"))); if (!equals(className, argRaw) && !equals(classAlias, argRaw)) { release(classAlias); - release(className); + release(className); release(argsVecRaw); return false; } release(classAlias); - release(className); + release(className); PersistentVector_iteratorNext(&it); } release(argsVecRaw); @@ -145,52 +149,55 @@ bool InvokeManager::checkIntrinsicArgs(PersistentArrayMap *description, TypedValue InvokeManager::generateIntrinsic(RTValue intrinsicDescription, const std::vector &args) { - std::vector argTypes; - for (auto arg : args) { - argTypes.push_back(arg.type.unboxed()); - } - + std::vector argTypes; + for (auto arg : args) { + argTypes.push_back(arg.type.unboxed()); + } + if (getType(intrinsicDescription) != persistentArrayMapType) { release(intrinsicDescription); - throwInternalInconsistencyException("Intrinsic call: description is not a map"); - } + throwInternalInconsistencyException( + "Intrinsic call: description is not a map"); + } PersistentArrayMap *description = - (PersistentArrayMap *)RT_unboxPtr(intrinsicDescription); + (PersistentArrayMap *)RT_unboxPtr(intrinsicDescription); Ptr_retain(description); try { /* TODO: Ultimately we will assume that arg count and types match at the - * moment of generation. For now check stays here for debugging. */ - Ptr_retain(description); + * moment of generation. For now check stays here for debugging. */ + Ptr_retain(description); if (!checkIntrinsicArgs(description, argTypes)) { throwInternalInconsistencyException("Args do not match"); } Ptr_retain(description); ObjectTypeSet returnValueTypeSet = returnValueType(description); - + RTValue intrinsicName = PersistentArrayMap_get( description, Keyword_create(String_create("symbol"))); if (getType(intrinsicName) != stringType) { release(intrinsicName); throwInternalInconsistencyException("Symbol is not a string"); } - String *compactifiedIntrinsicName = String_compactify((String *)RT_unboxPtr(intrinsicName)); + String *compactifiedIntrinsicName = + String_compactify((String *)RT_unboxPtr(intrinsicName)); std::string stringIntrinsicName(String_c_str(compactifiedIntrinsicName)); Ptr_release(compactifiedIntrinsicName); - Ptr_retain(description); + Ptr_retain(description); RTValue callType = PersistentArrayMap_get( description, Keyword_create(String_create("type"))); RTValue intriniscKeyword = Keyword_create(String_create("intrinsic")); RTValue callKeyword = Keyword_create(String_create("call")); - + if (equals(callType, intriniscKeyword)) { auto block = intrinsics.find(stringIntrinsicName); if (block == intrinsics.end()) { release(intriniscKeyword); release(callKeyword); - throwInternalInconsistencyException("Intrinsic '" + stringIntrinsicName + "' does not exist."); + throwInternalInconsistencyException( + "Intrinsic '" + stringIntrinsicName + "' does not exist."); } std::vector argVals; for (auto arg : args) { @@ -206,63 +213,69 @@ InvokeManager::generateIntrinsic(RTValue intrinsicDescription, release(intriniscKeyword); release(callKeyword); Ptr_release(description); - return invokeRuntime(stringIntrinsicName, &returnValueTypeSet, argTypes, args); + return invokeRuntime(stringIntrinsicName, &returnValueTypeSet, argTypes, + args); } else { - release(intriniscKeyword); + release(intriniscKeyword); release(callType); - throwInternalInconsistencyException("Unknown intrinsic type."); - } + throwInternalInconsistencyException("Unknown intrinsic type."); + } } catch (...) { Ptr_release(description); throw; - } -} - - TypedValue - InvokeManager::invokeRuntime(const std::string &fname, - const ObjectTypeSet *retValType, - const std::vector &argTypes, - const std::vector &args, - const bool isVariadic) { - if(argTypes.size() > args.size()) throwInternalInconsistencyException("Internal error: To litle arguments passed"); - std::vector llvmTypes; - for (auto &t : argTypes) { - llvmTypes.push_back(types.typeForType(t)); - } + } +} + +TypedValue InvokeManager::invokeRuntime( + const std::string &fname, const ObjectTypeSet *retValType, + const std::vector &argTypes, + const std::vector &args, const bool isVariadic) { + if (argTypes.size() > args.size()) + throwInternalInconsistencyException( + "Internal error: To litle arguments passed"); + std::vector llvmTypes; + for (auto &t : argTypes) { + llvmTypes.push_back(types.typeForType(t)); + } + + if (!isVariadic && argTypes.size() != args.size()) + throwInternalInconsistencyException("Internal error: Wrong arg count"); - if(!isVariadic && argTypes.size() != args.size()) throwInternalInconsistencyException("Internal error: Wrong arg count"); - - FunctionType *functionType = FunctionType::get( - retValType ? types.typeForType(*retValType) : types.voidTy, llvmTypes, isVariadic); + FunctionType *functionType = FunctionType::get( + retValType ? types.typeForType(*retValType) : types.voidTy, llvmTypes, + isVariadic); - Function *toCall = + Function *toCall = theModule.getFunction(fname) - ?: Function::Create(functionType, Function::ExternalLinkage, - fname, theModule); - - vector argVals; - for (size_t i = 0; i < args.size(); i++) { - auto &arg = args[i]; - if (i < argTypes.size()) { - auto &argType = argTypes[i]; - if (!argType.isBoxedType()) { - if(!arg.type.isDetermined()) - throwInternalInconsistencyException(std::format( - "Internal error: Types mismatch for runtime function " - "call: for {}th arg required is {} and found {}.", - i, argType.toString(), arg.type.toString())); - argVals.push_back(valueEncoder.unbox(arg).value); - } else { - /* Each boxed is allowed */ - argVals.push_back(valueEncoder.box(arg).value); + ?: Function::Create(functionType, Function::ExternalLinkage, fname, + theModule); + + vector argVals; + for (size_t i = 0; i < args.size(); i++) { + auto &arg = args[i]; + if (i < argTypes.size()) { + auto &argType = argTypes[i]; + if (!argType.isBoxedType()) { + if (!arg.type.isDetermined()) { + std::ostringstream oss; + oss << "Internal error: Types mismatch for runtime function call: " + "for " + << i << "th arg required is " << argType.toString() + << " and found " << arg.type.toString() << "."; + throwInternalInconsistencyException(oss.str()); } + argVals.push_back(valueEncoder.unbox(arg).value); } else { - /* Varargs are always boxed */ - argVals.push_back(valueEncoder.box(arg).value); - } + /* Each boxed is allowed */ + argVals.push_back(valueEncoder.box(arg).value); + } + } else { + /* Varargs are always boxed */ + argVals.push_back(valueEncoder.box(arg).value); } - return TypedValue( - retValType ? *retValType : ObjectTypeSet::all(), - builder.CreateCall(toCall, argVals, std::string("call_") + fname)); } + return TypedValue( + retValType ? *retValType : ObjectTypeSet::all(), + builder.CreateCall(toCall, argVals, std::string("call_") + fname)); } +} // namespace rt diff --git a/backend-v2/jit/JITEngine.cpp b/backend-v2/jit/JITEngine.cpp index 20ffd7b5..082cff91 100644 --- a/backend-v2/jit/JITEngine.cpp +++ b/backend-v2/jit/JITEngine.cpp @@ -14,13 +14,20 @@ JITEngine::JITEngine(ThreadsafeCompilerState &state, size_t numThreads) if (!JITExp) throw std::runtime_error("Failed to init LLJIT"); jit = std::move(*JITExp); + + // Expose symbols from the current process to the JIT. + // This is required on Linux to find runtime functions. + auto &DL = jit->getDataLayout(); + jit->getMainJITDylib().addGenerator( + cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( + DL.getGlobalPrefix()))); } std::future JITEngine::compileAST(const Node &AST, const std::string &moduleName, llvm::OptimizationLevel level, bool printModule) { // Wrap logic in a task for the pool - return pool.enqueue([this, AST, level, moduleName, + return pool.enqueue([this, AST, moduleName, printModule]() -> llvm::orc::ExecutorAddr { auto codeGenerator = CodeGen(moduleName, threadsafeState); auto fName = codeGenerator.codegenTopLevel(AST); diff --git a/backend-v2/jit/JITEngine.h b/backend-v2/jit/JITEngine.h index 93e1cf0d..613b5799 100644 --- a/backend-v2/jit/JITEngine.h +++ b/backend-v2/jit/JITEngine.h @@ -23,10 +23,10 @@ #include "llvm/Support/raw_ostream.h" // Standard Library -#include "../bytecode.pb.h" #include "../codegen/CodeGen.h" #include "../state/ThreadsafeCompilerState.h" #include "../tools/ThreadPool.h" +#include "bytecode.pb.h" #include #include #include diff --git a/backend-v2/main.cpp b/backend-v2/main.cpp index e557b2ae..24ffd829 100644 --- a/backend-v2/main.cpp +++ b/backend-v2/main.cpp @@ -1,10 +1,12 @@ -#include -#include #include "RuntimeHeaders.h" #include "bytecode.pb.h" +#include +#include +#include "bridge/Exceptions.h" #include "llvm/ADT/APFloat.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/DebugInfo/Symbolize/Symbolize.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DerivedTypes.h" @@ -20,33 +22,22 @@ #include "llvm/Transforms/InstCombine/InstCombine.h" #include "llvm/Transforms/Scalar.h" #include "llvm/Transforms/Scalar/GVN.h" -#include "llvm/DebugInfo/Symbolize/Symbolize.h" -#include "bridge/Exceptions.h" -#include "state/ThreadsafeCompilerState.h" #include "jit/JITEngine.h" +#include "state/ThreadsafeCompilerState.h" using namespace std; using namespace llvm; #include "RuntimeHeaders.h" -#include -std::string getSelfExecutablePath() { - char path[1024]; - uint32_t size = sizeof(path); - if (_NSGetExecutablePath(path, &size) == 0) - return std::string(path); - return ""; -} - int main(int argc, char *argv[]) { - setbuf(stdout, NULL); - if(argc != 2) { + setbuf(stdout, NULL); + if (argc != 2) { cout << "Please specify the filename for compilation" << endl; return -1; } - + GOOGLE_PROTOBUF_VERIFY_VERSION; clojure::rt::protobuf::bytecode::Programme astInterfaces; @@ -66,7 +57,7 @@ int main(int argc, char *argv[]) { return -1; } } - + clojure::rt::protobuf::bytecode::Programme astRoot; { fstream input(argv[1], ios::in | ios::binary); @@ -75,38 +66,34 @@ int main(int argc, char *argv[]) { return -1; } } - + initialise_memory(); try { rt::ThreadsafeCompilerState state; rt::JITEngine engine(state); - RTValue interfaces = - engine.compileAST(astInterfaces.nodes(0), "__interfaces", - llvm::OptimizationLevel::O0, - false) - .get() - .toPtr()(); + RTValue interfaces = engine + .compileAST(astInterfaces.nodes(0), "__interfaces", + llvm::OptimizationLevel::O0, false) + .get() + .toPtr()(); state.storeInternalProtocols(interfaces); - - RTValue classes = - engine.compileAST(astClasses.nodes(0), "__classes", - llvm::OptimizationLevel::O0, - false) - .get() - .toPtr()(); + + RTValue classes = engine + .compileAST(astClasses.nodes(0), "__classes", + llvm::OptimizationLevel::O0, false) + .get() + .toPtr()(); state.storeInternalClasses(classes); - - - -// release(intrinsics); - - // auto f = engine.compileAST(astRoot.nodes(0), "__root", llvm::OptimizationLevel::O0); - // cout << "Compiling!!!" << endl; - // printReferenceCounts(); + + // release(intrinsics); + + // auto f = engine.compileAST(astRoot.nodes(0), "__root", + // llvm::OptimizationLevel::O0); cout << "Compiling!!!" << endl; + // printReferenceCounts(); // RTValue whaat = res(); // printReferenceCounts(); @@ -119,18 +106,8 @@ int main(int argc, char *argv[]) { // Ptr_release(s); // printReferenceCounts(); } catch (rt::LanguageException e) { - llvm::symbolize::LLVMSymbolizer::Options options; - options.Demangle = true; - options.PrintFunctions = llvm::symbolize::FunctionNameKind::LinkageName; -// options.DsymHints.push_back(getSelfExecutablePath() + ".dSYM/Contents/Resources/DWARF/" + "clojure-rt"); - llvm::symbolize::LLVMSymbolizer symbolizer(options); - cout << e.toString(symbolizer, - getSelfExecutablePath() + - ".dSYM/Contents/Resources/DWARF/" + "clojure-rt", - _dyld_get_image_vmaddr_slide(0)) - << endl; - //e.printRawTrace(); - } + cout << rt::getExceptionString(e) << endl; + } return 0; -} +} diff --git a/backend-v2/runtime/BigInteger.c b/backend-v2/runtime/BigInteger.c index f8c93317..4a205ad6 100644 --- a/backend-v2/runtime/BigInteger.c +++ b/backend-v2/runtime/BigInteger.c @@ -1,35 +1,36 @@ #include "BigInteger.h" -#include "String.h" #include "Object.h" +#include "String.h" -BigInteger* BigInteger_createUninitialized() { - BigInteger *self = (BigInteger *)allocate(sizeof(BigInteger)); +BigInteger *BigInteger_createUninitialized() { + BigInteger *self = (BigInteger *)allocate(sizeof(BigInteger)); Object_create((Object *)self, bigIntegerType); return self; } -BigInteger* BigInteger_createUnassigned() { +BigInteger *BigInteger_createUnassigned() { BigInteger *self = BigInteger_createUninitialized(); mpz_init(self->value); return self; } /* mem done */ -BigInteger* BigInteger_createFromStr(const char * value) { +BigInteger *BigInteger_createFromStr(const char *value) { BigInteger *self = BigInteger_createUninitialized(); - assert(mpz_init_set_str(self->value, value, 10) == 0 && "Failed to initialize BigInteger"); + assert(mpz_init_set_str(self->value, value, 10) == 0 && + "Failed to initialize BigInteger"); return self; } /* mem done */ -BigInteger* BigInteger_createFromMpz(mpz_t value) { +BigInteger *BigInteger_createFromMpz(mpz_t value) { BigInteger *self = BigInteger_createUninitialized(); mpz_init_set(self->value, value); return self; } /* mem done */ -BigInteger* BigInteger_createFromInt(word_t value) { +BigInteger *BigInteger_createFromInt(word_t value) { BigInteger *self = BigInteger_createUninitialized(); mpz_init_set_si(self->value, value); return self; @@ -47,8 +48,10 @@ uword_t BigInteger_hash(BigInteger *self) { } /* mem done */ -String *BigInteger_toString(BigInteger *self) { - String *num = String_create(mpz_get_str(NULL, 10, self->value)); +String *BigInteger_toString(BigInteger *self) { + char *str = mpz_get_str(NULL, 10, self->value); + String *num = String_createDynamicStr(str); + free(str); String *suffix = String_create("N"); String *retVal = String_concat(num, suffix); Ptr_release(self); @@ -56,9 +59,7 @@ String *BigInteger_toString(BigInteger *self) { } /* outside refcount system */ -void BigInteger_destroy(BigInteger *self) { - mpz_clear(self->value); -} +void BigInteger_destroy(BigInteger *self) { mpz_clear(self->value); } double BigInteger_toDouble(BigInteger *self) { double retVal = mpz_get_d(self->value); @@ -70,13 +71,21 @@ BigInteger *BigInteger_add(BigInteger *self, BigInteger *other) { bool selfReusable = Ptr_isReusable(self); bool otherReusable = Ptr_isReusable(other); BigInteger *retVal; - if (selfReusable) retVal = self; - else if (otherReusable) retVal = other; - else retVal = BigInteger_createUnassigned(); + if (selfReusable) + retVal = self; + else if (otherReusable) + retVal = other; + else + retVal = BigInteger_createUnassigned(); mpz_add(retVal->value, self->value, other->value); - if (selfReusable) Ptr_release(other); - else if (otherReusable) Ptr_release(self); - else { Ptr_release(self); Ptr_release(other); } + if (selfReusable) + Ptr_release(other); + else if (otherReusable) + Ptr_release(self); + else { + Ptr_release(self); + Ptr_release(other); + } return retVal; } @@ -84,13 +93,21 @@ BigInteger *BigInteger_sub(BigInteger *self, BigInteger *other) { bool selfReusable = Ptr_isReusable(self); bool otherReusable = Ptr_isReusable(other); BigInteger *retVal; - if (selfReusable) retVal = self; - else if (otherReusable) retVal = other; - else retVal = BigInteger_createUnassigned(); + if (selfReusable) + retVal = self; + else if (otherReusable) + retVal = other; + else + retVal = BigInteger_createUnassigned(); mpz_sub(retVal->value, self->value, other->value); - if (selfReusable) Ptr_release(other); - else if (otherReusable) Ptr_release(self); - else { Ptr_release(self); Ptr_release(other); } + if (selfReusable) + Ptr_release(other); + else if (otherReusable) + Ptr_release(self); + else { + Ptr_release(self); + Ptr_release(other); + } return retVal; } @@ -98,13 +115,21 @@ BigInteger *BigInteger_mul(BigInteger *self, BigInteger *other) { bool selfReusable = Ptr_isReusable(self); bool otherReusable = Ptr_isReusable(other); BigInteger *retVal; - if (selfReusable) retVal = self; - else if (otherReusable) retVal = other; - else retVal = BigInteger_createUnassigned(); + if (selfReusable) + retVal = self; + else if (otherReusable) + retVal = other; + else + retVal = BigInteger_createUnassigned(); mpz_mul(retVal->value, self->value, other->value); - if (selfReusable) Ptr_release(other); - else if (otherReusable) Ptr_release(self); - else { Ptr_release(self); Ptr_release(other); } + if (selfReusable) + Ptr_release(other); + else if (otherReusable) + Ptr_release(self); + else { + Ptr_release(self); + Ptr_release(other); + } return retVal; } @@ -114,18 +139,26 @@ void *BigInteger_div(BigInteger *self, BigInteger *other) { Ptr_release(other); return NULL; // Exception: divide by zero } - + if (mpz_divisible_p(self->value, other->value)) { bool selfReusable = Ptr_isReusable(self); bool otherReusable = Ptr_isReusable(other); - BigInteger * retVal; - if (selfReusable) retVal = self; - else if (otherReusable) retVal = other; - else retVal = BigInteger_createUnassigned(); + BigInteger *retVal; + if (selfReusable) + retVal = self; + else if (otherReusable) + retVal = other; + else + retVal = BigInteger_createUnassigned(); mpz_divexact(retVal->value, self->value, other->value); - if (selfReusable) Ptr_release(other); - else if (otherReusable) Ptr_release(self); - else { Ptr_release(self); Ptr_release(other); } + if (selfReusable) + Ptr_release(other); + else if (otherReusable) + Ptr_release(self); + else { + Ptr_release(self); + Ptr_release(other); + } return retVal; } else { Ratio *ratio = Ratio_createUnassigned(); diff --git a/backend-v2/runtime/CMakeLists.txt b/backend-v2/runtime/CMakeLists.txt index a97ac00e..60ab5372 100644 --- a/backend-v2/runtime/CMakeLists.txt +++ b/backend-v2/runtime/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.26) project(runtime) -set(CMAKE_OSX_ARCHITECTURES "arm64") if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) diff --git a/backend-v2/runtime/ConcurrentHashMap.h b/backend-v2/runtime/ConcurrentHashMap.h index 2acd71e0..4b66281e 100644 --- a/backend-v2/runtime/ConcurrentHashMap.h +++ b/backend-v2/runtime/ConcurrentHashMap.h @@ -1,9 +1,27 @@ #ifndef RT_CONCURRENT_HASH_MAP #define RT_CONCURRENT_HASH_MAP +#ifdef __cplusplus +#include +using std::atomic_compare_exchange_strong_explicit; +using std::atomic_compare_exchange_weak_explicit; +using std::atomic_exchange_explicit; +using std::atomic_fetch_add_explicit; +using std::atomic_fetch_sub_explicit; +using std::atomic_load_explicit; +using std::atomic_store_explicit; +using std::memory_order; +using std::memory_order_acq_rel; +using std::memory_order_acquire; +using std::memory_order_relaxed; +using std::memory_order_release; +using std::memory_order_seq_cst; +#define _Atomic(X) std::atomic +#else #include -#include "defines.h" -#include "String.h" +#endif #include "RTValue.h" +#include "String.h" +#include "defines.h" /* https://preshing.com/20160201/new-concurrent-hash-maps-for-cpp/ Leapfrog */ @@ -14,10 +32,10 @@ typedef struct Object Object; #define CHM_EMPTY 0 typedef struct ConcurrentHashMapEntry { - RTValue _Atomic key; - RTValue _Atomic value; - _Atomic uword_t keyHash; - _Atomic unsigned short leaps; + _Atomic(RTValue) key; + _Atomic(RTValue) value; + _Atomic(uword_t) keyHash; + _Atomic(unsigned short) leaps; } ConcurrentHashMapEntry; typedef struct ConcurrentHashMapNode { @@ -28,17 +46,18 @@ typedef struct ConcurrentHashMapNode { typedef struct ConcurrentHashMap { Object super; - ConcurrentHashMapNode * _Atomic root; + _Atomic(ConcurrentHashMapNode *) root; } ConcurrentHashMap; - ConcurrentHashMap *ConcurrentHashMap_create(unsigned char initialSizeExponent); -void ConcurrentHashMap_assoc(ConcurrentHashMap *self, RTValue key, RTValue value); +void ConcurrentHashMap_assoc(ConcurrentHashMap *self, RTValue key, + RTValue value); void ConcurrentHashMap_dissoc(ConcurrentHashMap *self, RTValue key); RTValue ConcurrentHashMap_get(ConcurrentHashMap *self, RTValue key); -bool ConcurrentHashMap_equals(ConcurrentHashMap *self, ConcurrentHashMap *other); +bool ConcurrentHashMap_equals(ConcurrentHashMap *self, + ConcurrentHashMap *other); uword_t ConcurrentHashMap_hash(ConcurrentHashMap *self); String *ConcurrentHashMap_toString(ConcurrentHashMap *self); void ConcurrentHashMap_destroy(ConcurrentHashMap *self); diff --git a/backend-v2/runtime/Keyword.c b/backend-v2/runtime/Keyword.c index 2a4da085..99151b10 100644 --- a/backend-v2/runtime/Keyword.c +++ b/backend-v2/runtime/Keyword.c @@ -9,37 +9,37 @@ extern ConcurrentHashMap *keywords; extern ConcurrentHashMap *keywordsInverted; static pthread_mutex_t intern_mutex = PTHREAD_MUTEX_INITIALIZER; -static _Atomic uint32_t minUnusedKeyword = 1; +static _Atomic(uint32_t) minUnusedKeyword = 1; -/* mem done */ +/* mem done - consumes string */ RTValue Keyword_create(String *string) { RTValue stringVal = RT_boxPtr(string); - Ptr_retain(string); - RTValue retVal = ConcurrentHashMap_get(keywords, stringVal); + Ptr_retain(string); /* +1 for first get */ + RTValue retVal = ConcurrentHashMap_get(keywords, stringVal); /* consumes 1 */ if (RT_isNil(retVal)) { - Ptr_retain(string); + Ptr_retain(string); /* +1 for second get */ pthread_mutex_lock(&intern_mutex); - /* interning */ - RTValue retVal2 = ConcurrentHashMap_get(keywords, stringVal); + RTValue retVal2 = + ConcurrentHashMap_get(keywords, stringVal); /* consumes 1 */ if (RT_isKeyword(retVal2)) { pthread_mutex_unlock(&intern_mutex); - Ptr_release(string); + Ptr_release(string); /* consume caller's ref */ return retVal2; } RTValue new = RT_boxKeyword( atomic_fetch_add_explicit(&minUnusedKeyword, 1, memory_order_relaxed)); + /* Need 2 refs for 2 assoc calls (each consumes stringVal as key/value). + We currently hold 1 (caller's), so retain once more. */ Ptr_retain(string); - ConcurrentHashMap_assoc(keywordsInverted, new, stringVal); - Ptr_retain(string); - ConcurrentHashMap_assoc(keywords, stringVal, new); + ConcurrentHashMap_assoc(keywordsInverted, new, stringVal); /* consumes 1 */ + ConcurrentHashMap_assoc(keywords, stringVal, new); /* consumes 1 */ pthread_mutex_unlock(&intern_mutex); - Ptr_release(string); return new; } - Ptr_release(string); + Ptr_release(string); /* consume caller's ref */ return retVal; } diff --git a/backend-v2/runtime/Object.c b/backend-v2/runtime/Object.c index 64a4d52f..c5f1a758 100644 --- a/backend-v2/runtime/Object.c +++ b/backend-v2/runtime/Object.c @@ -1,17 +1,16 @@ #include "Object.h" -#include -#include +#include "RTValue.h" +#include "RuntimeInterface.h" #include #include -#include "RuntimeInterface.h" -#include "RTValue.h" +#include +#include -_Atomic uword_t allocationCount[256]; -_Atomic uword_t objectCount[256]; +_Atomic(uword_t) allocationCount[256]; +_Atomic(uword_t) objectCount[256]; _Thread_local void *memoryBank[8] = {0}; _Thread_local int memoryBankSize[8] = {0}; - /* pool globalPool1; */ /* pool globalPool2; */ /* pool globalPool3; */ @@ -23,7 +22,8 @@ _Thread_local int memoryBankSize[8] = {0}; void PersistentVector_initialise(); void initialise_memory() { - for(int i=0; i<200; i++) atomic_exchange(&(allocationCount[i]), 0); + for (int i = 0; i < 200; i++) + atomic_exchange(&(allocationCount[i]), 0); /* poolInitialize(&globalPool1, BLOCK_SIZE, 100000); */ /* poolInitialize(&globalPool2, 128, 100000); */ /* poolInitialize(&globalPool3, 64, 100000); */ @@ -45,25 +45,26 @@ void initialise_memory() { /* } */ extern void *allocate(size_t size); -extern void deallocate(void * restrict ptr); +extern void deallocate(void *restrict ptr); extern void retain(RTValue self); extern bool release(RTValue self); extern void autorelease(RTValue self); -extern bool release_internal(void * restrict self, bool deallocatesChildren); +extern bool release_internal(void *restrict self, bool deallocatesChildren); extern bool equals(RTValue self, RTValue other); extern uword_t hash(RTValue self); extern String *toString(RTValue self); -extern void Object_create(Object * restrict self, objectType type); -extern void Object_retain(Object * restrict self); -extern bool Object_release(Object * restrict self); -extern bool Object_release_internal(Object * restrict self, bool deallocatesChildren); +extern void Object_create(Object *restrict self, objectType type); +extern void Object_retain(Object *restrict self); +extern bool Object_release(Object *restrict self); +extern bool Object_release_internal(Object *restrict self, + bool deallocatesChildren); extern void Object_destroy(Object *restrict self, bool deallocateChildren); -extern void Object_autorelease(Object * restrict self); -extern bool Object_equals(Object * restrict self, Object * restrict other); -extern uword_t Object_hash(Object * restrict self); -extern String *Object_toString(Object * restrict self); +extern void Object_autorelease(Object *restrict self); +extern bool Object_equals(Object *restrict self, Object *restrict other); +extern uword_t Object_hash(Object *restrict self); +extern String *Object_toString(Object *restrict self); extern bool Object_isReusable(Object *restrict self); extern bool isReusable(RTValue self); extern objectType getType(RTValue obj); diff --git a/backend-v2/runtime/Object.h b/backend-v2/runtime/Object.h index a55f7c93..60401e17 100644 --- a/backend-v2/runtime/Object.h +++ b/backend-v2/runtime/Object.h @@ -5,44 +5,62 @@ #pragma clang diagnostic ignored "-Wnullability-completeness" #pragma clang diagnostic ignored "-Wexpansion-to-defined" -#include -#include +#include "PersistentVectorIterator.h" +#include "RTValue.h" +#include "defines.h" +#include +#include #include +#include #include +#include #include -#include -#include -#include "RTValue.h" -#include "defines.h" -#include "PersistentVectorIterator.h" +#ifdef __cplusplus +#include +using std::atomic_compare_exchange_strong_explicit; +using std::atomic_compare_exchange_weak_explicit; +using std::atomic_exchange_explicit; +using std::atomic_fetch_add_explicit; +using std::atomic_fetch_sub_explicit; +using std::atomic_load_explicit; +using std::atomic_store_explicit; +using std::memory_order; +using std::memory_order_acq_rel; +using std::memory_order_acquire; +using std::memory_order_relaxed; +using std::memory_order_release; +using std::memory_order_seq_cst; +#define _Atomic(X) std::atomic +#else #include -#include "String.h" -#include "Integer.h" -#include "Double.h" +#endif +#include "BigInteger.h" #include "Boolean.h" +#include "Class.h" +#include "ConcurrentHashMap.h" +#include "Double.h" +#include "Function.h" +#include "Integer.h" +#include "Keyword.h" #include "Nil.h" +#include "PersistentArrayMap.h" #include "PersistentList.h" #include "PersistentVector.h" -#include "PersistentVectorNode.h" #include "PersistentVectorIterator.h" -#include "ConcurrentHashMap.h" -#include "Symbol.h" -#include "Keyword.h" -#include "Function.h" -#include "BigInteger.h" +#include "PersistentVectorNode.h" #include "Ratio.h" -#include "Class.h" -#include "PersistentArrayMap.h" +#include "String.h" +#include "Symbol.h" -typedef struct String String; +typedef struct String String; #define MEMORY_BANK_SIZE_MAX 10 extern void logBacktrace(); void printReferenceCounts(); -extern _Atomic uword_t allocationCount[256]; -extern _Atomic uword_t objectCount[256]; +extern _Atomic(uword_t) allocationCount[256]; +extern _Atomic(uword_t) objectCount[256]; // bank 0 - 32 bytes 2^5 // bank 1 - 64 bytes @@ -58,49 +76,60 @@ extern _Thread_local int memoryBankSize[8]; void initialise_memory(); - inline void *allocate(size_t size) { #ifndef USE_MEMORY_BANKS - return malloc(size); -#else - int bankId; - if (size <= 24) bankId = 0; - else if (size <= 64) bankId = 1; - else if (size <= 128) bankId = 2; - else if (size <= 256) bankId = 3; - else if (size <= 296) bankId = 4; - else if (size <= 1024) bankId = 5; - else if (size <= 2048) bankId = 6; - else if (size <= 4096) bankId = 7; - else { - // Not from a bank: - printf("Not from bank! : %zu\n", size); - Object *hdr = (Object *)malloc(size); - if (!hdr) return NULL; - hdr->bankId = 0xFF; // 0xFF marks a non-bank allocation - return hdr; - } - - void *candidate = memoryBank[bankId]; - if (candidate) { -// printf("Found in bank %d : %zu - %p\n", bankId, size, candidate); - memoryBank[bankId] = *((void **)candidate); - memoryBankSize[bankId]--; - // candidate already points to user space which is after the header - return candidate; - } - - // No free block in bank, allocate a new one - size_t blockSize; - if(bankId == 4) blockSize = 296; - else if(bankId == 0) blockSize = 24; - else { - blockSize = 1 << (5 + bankId); - } - Object *hdr = (Object *)malloc(blockSize); - if (!hdr) return NULL; - hdr->bankId = (unsigned char)bankId; + return malloc(size); +#else + int bankId; + if (size <= 24) + bankId = 0; + else if (size <= 64) + bankId = 1; + else if (size <= 128) + bankId = 2; + else if (size <= 256) + bankId = 3; + else if (size <= 296) + bankId = 4; + else if (size <= 1024) + bankId = 5; + else if (size <= 2048) + bankId = 6; + else if (size <= 4096) + bankId = 7; + else { + // Not from a bank: + printf("Not from bank! : %zu\n", size); + Object *hdr = (Object *)malloc(size); + if (!hdr) + return NULL; + hdr->bankId = 0xFF; // 0xFF marks a non-bank allocation return hdr; + } + + void *candidate = memoryBank[bankId]; + if (candidate) { + // printf("Found in bank %d : %zu - %p\n", bankId, size, candidate); + memoryBank[bankId] = *((void **)candidate); + memoryBankSize[bankId]--; + // candidate already points to user space which is after the header + return candidate; + } + + // No free block in bank, allocate a new one + size_t blockSize; + if (bankId == 4) + blockSize = 296; + else if (bankId == 0) + blockSize = 24; + else { + blockSize = 1 << (5 + bankId); + } + Object *hdr = (Object *)malloc(blockSize); + if (!hdr) + return NULL; + hdr->bankId = (unsigned char)bankId; + return hdr; #endif } @@ -109,98 +138,111 @@ inline void deallocate(void *ptr) { free(ptr); return; #else - if (!ptr) return; - Object *hdr = (Object *)ptr; - unsigned char bankId = hdr->bankId; - - // If it's not from a bank, free directly - if (bankId == 0xFF || memoryBankSize[bankId] >= MEMORY_BANK_SIZE_MAX) { - free(hdr); - return; - } -// printf("Pushing to memory bank %d - %p\n", bankId, ptr); - // Push back into the memory bank free list - *((void **)ptr) = memoryBank[bankId]; - memoryBank[bankId] = ptr; - memoryBankSize[bankId]++; + if (!ptr) + return; + Object *hdr = (Object *)ptr; + unsigned char bankId = hdr->bankId; + + // If it's not from a bank, free directly + if (bankId == 0xFF || memoryBankSize[bankId] >= MEMORY_BANK_SIZE_MAX) { + free(hdr); + return; + } + // printf("Pushing to memory bank %d - %p\n", bankId, ptr); + // Push back into the memory bank free list + *((void **)ptr) = memoryBank[bankId]; + memoryBank[bankId] = ptr; + memoryBankSize[bankId]++; #endif } /* Outside of refcount system */ inline objectType getType(RTValue v) { - if (RT_isDouble(v)) return doubleType; - if (RT_isInt32(v)) return integerType; - if (RT_isBool(v)) return booleanType; - if (RT_isNil(v)) return nilType; - if (RT_isKeyword(v)) return keywordType; - if (RT_isSymbol(v)) return symbolType; + if (RT_isDouble(v)) + return doubleType; + if (RT_isInt32(v)) + return integerType; + if (RT_isBool(v)) + return booleanType; + if (RT_isNil(v)) + return nilType; + if (RT_isKeyword(v)) + return keywordType; + if (RT_isSymbol(v)) + return symbolType; assert(RT_isPtr(v) && "Internal error: Not a pointer"); - return ((Object*)RT_unboxPtr(v))->type; + return ((Object *)RT_unboxPtr(v))->type; } inline void Object_retain(Object *restrict self) { // printf("RETAIN!!! %d\n", self->type); #ifdef REFCOUNT_TRACING - atomic_fetch_add_explicit(&(allocationCount[self->type-1]), 1, memory_order_relaxed); + atomic_fetch_add_explicit(&(allocationCount[self->type - 1]), 1, + memory_order_relaxed); #endif #ifdef REFCOUNT_NONATOMIC self->refCount++; -#else +#else atomic_fetch_add_explicit(&(self->atomicRefCount), 1, memory_order_relaxed); #endif } inline void Object_destroy(Object *restrict self, bool deallocateChildren) { -// printf("--> Deallocating type %d addres %lld\n", self->type, (uword_t)); - // printReferenceCounts(); - switch((objectType)self->type) { + // printf("--> Deallocating type %d addres %lld\n", self->type, (uword_t)); + // printReferenceCounts(); + switch ((objectType)self->type) { case stringType: - String_destroy((String *) self); + String_destroy((String *)self); break; case classType: - Class_destroy((Class *) self); - break; + Class_destroy((Class *)self); + break; case persistentListType: PersistentList_destroy((PersistentList *)self, deallocateChildren); - break; + break; case persistentVectorType: PersistentVector_destroy((PersistentVector *)self, deallocateChildren); break; case persistentVectorNodeType: - PersistentVectorNode_destroy((PersistentVectorNode *)self, deallocateChildren); + PersistentVectorNode_destroy((PersistentVectorNode *)self, + deallocateChildren); break; case concurrentHashMapType: - ConcurrentHashMap_destroy((ConcurrentHashMap *) self); + ConcurrentHashMap_destroy((ConcurrentHashMap *)self); break; case functionType: - Function_destroy((ClojureFunction *) self); + Function_destroy((ClojureFunction *)self); break; case bigIntegerType: - BigInteger_destroy((BigInteger *) self); + BigInteger_destroy((BigInteger *)self); break; case ratioType: - Ratio_destroy((Ratio *) self); + Ratio_destroy((Ratio *)self); break; case persistentArrayMapType: PersistentArrayMap_destroy((PersistentArrayMap *)self, deallocateChildren); break; default: - assert(false && "Internal error: hash computation for NaN tagged types should be computed earlier."); + assert(false && "Internal error: hash computation for NaN tagged types " + "should be computed earlier."); } deallocate(self); - // printf("dealloc end %lld\n", (uword_t)); - // printReferenceCounts(); - // printf("=========================\n"); + // printf("dealloc end %lld\n", (uword_t)); + // printReferenceCounts(); + // printf("=========================\n"); } inline bool Object_isReusable(Object *restrict self) { - uword_t refCount = atomic_load_explicit(&(self->atomicRefCount), memory_order_acquire); - // Multithreading - is it really safe to assume it is reusable if refcount is 1? - /* The reasoning here is that passing object to another thread is an operation that by - itself increases its reference count. Therefore it is assumed that if recount is 1 at - a point in time this means other threads have no knowledge of this object's existence - at this particular moment. Therefore, if only our thread knows of it, it can pass it to another - but only after it completes current operation. + uword_t refCount = + atomic_load_explicit(&(self->atomicRefCount), memory_order_acquire); + // Multithreading - is it really safe to assume it is reusable if refcount is + // 1? + /* The reasoning here is that passing object to another thread is an + operation that by itself increases its reference count. Therefore it is + assumed that if recount is 1 at a point in time this means other threads + have no knowledge of this object's existence at this particular moment. + Therefore, if only our thread knows of it, it can pass it to another but + only after it completes current operation. */ return refCount == 1; } @@ -209,94 +251,105 @@ inline bool isReusable(RTValue self) { return RT_isPtr(self) && Object_isReusable((Object *)RT_unboxPtr(self)); } -inline bool Object_release_internal(Object *restrict self, bool deallocateChildren) { +inline bool Object_release_internal(Object *restrict self, + bool deallocateChildren) { #ifdef REFCOUNT_TRACING -// printf("RELEASE!!! %p %d %d\n", self, self->type, deallocateChildren); - atomic_fetch_sub_explicit(&(allocationCount[self->type - 1]), 1, memory_order_relaxed); + // printf("RELEASE!!! %p %d %d\n", self, self->type, deallocateChildren); + atomic_fetch_sub_explicit(&(allocationCount[self->type - 1]), 1, + memory_order_relaxed); #ifndef REFCOUNT_NONATOMIC - assert(atomic_load(&(self->atomicRefCount)) > 0); + assert(atomic_load(&(self->atomicRefCount)) > 0); #else - assert(self->refCount > 0); + assert(self->refCount > 0); #endif #endif #ifdef REFCOUNT_NONATOMIC if (--self->refCount == 0) { #else - uword_t relVal = atomic_fetch_sub_explicit(&(self->atomicRefCount), 1, memory_order_release); + uword_t relVal = atomic_fetch_sub_explicit(&(self->atomicRefCount), 1, + memory_order_release); assert(relVal >= 1 && "Memory corruption!"); if (relVal == 1) { #ifdef REFCOUNT_TRACING - uword_t countVal = atomic_fetch_sub_explicit(&(objectCount[self->type -1 ]), 1, memory_order_relaxed); + uword_t countVal = atomic_fetch_sub_explicit(&(objectCount[self->type - 1]), + 1, memory_order_relaxed); assert(countVal >= 1 && "Memory corruption!"); #endif #endif - atomic_thread_fence(memory_order_acquire); + atomic_thread_fence(memory_order_acquire); Object_destroy(self, deallocateChildren); return true; } return false; } - /* Outside of refcount system */ inline uword_t Object_hash(Object *restrict self) { - switch((objectType)self->type) { - case stringType: - return String_hash((String *) self); - break; - case classType: - return Class_hash((Class *)self); - break; - case persistentListType: - return PersistentList_hash((PersistentList *) self); - break; - case persistentVectorType: - return PersistentVector_hash((PersistentVector *) self); - break; - case persistentVectorNodeType: - return PersistentVectorNode_hash((PersistentVectorNode *) self); - break; - case concurrentHashMapType: - return ConcurrentHashMap_hash((ConcurrentHashMap *) self); - case functionType: - return Function_hash((ClojureFunction *) self); - case bigIntegerType: - return BigInteger_hash((BigInteger *) self); - case ratioType: - return Ratio_hash((Ratio *) self); - case persistentArrayMapType: - return PersistentArrayMap_hash((PersistentArrayMap *)self); - default: - assert(false && "Internal error: hash computation for NaN tagged types should be computed earlier."); - } + switch ((objectType)self->type) { + case stringType: + return String_hash((String *)self); + break; + case classType: + return Class_hash((Class *)self); + break; + case persistentListType: + return PersistentList_hash((PersistentList *)self); + break; + case persistentVectorType: + return PersistentVector_hash((PersistentVector *)self); + break; + case persistentVectorNodeType: + return PersistentVectorNode_hash((PersistentVectorNode *)self); + break; + case concurrentHashMapType: + return ConcurrentHashMap_hash((ConcurrentHashMap *)self); + case functionType: + return Function_hash((ClojureFunction *)self); + case bigIntegerType: + return BigInteger_hash((BigInteger *)self); + case ratioType: + return Ratio_hash((Ratio *)self); + case persistentArrayMapType: + return PersistentArrayMap_hash((PersistentArrayMap *)self); + default: + assert(false && "Internal error: hash computation for NaN tagged types " + "should be computed earlier."); + } } /* Outside of refcount system */ inline uword_t hash(RTValue v) { - objectType t = getType(v); - switch(t) { - case integerType: return (uword_t)RT_unboxInt32(v) + 1; - case booleanType: return (uword_t)RT_unboxBool(v) + 1; - case nilType: return 0xDEADBEEF; // Standard nil hash - case keywordType: return (uword_t)RT_unboxKeyword(v); - case symbolType: return (uword_t)RT_unboxSymbol(v); - case doubleType: { - double d = RT_unboxDouble(v); - uint64_t u; - memcpy(&u, &d, 8); - return u + 1; - } - default: - assert(RT_isPtr(v) && "Internal error - trying to compute hash from an unknown value"); - return Object_hash((Object*)RT_unboxPtr(v)); - } + objectType t = getType(v); + switch (t) { + case integerType: + return (uword_t)RT_unboxInt32(v) + 1; + case booleanType: + return (uword_t)RT_unboxBool(v) + 1; + case nilType: + return 0xDEADBEEF; // Standard nil hash + case keywordType: + return (uword_t)RT_unboxKeyword(v); + case symbolType: + return (uword_t)RT_unboxSymbol(v); + case doubleType: { + double d = RT_unboxDouble(v); + uint64_t u; + memcpy(&u, &d, 8); + return u + 1; + } + default: + assert(RT_isPtr(v) && + "Internal error - trying to compute hash from an unknown value"); + return Object_hash((Object *)RT_unboxPtr(v)); + } } /* Outside of refcount system */ inline bool Object_equals(Object *self, Object *other) { - if (Object_hash(self) != Object_hash(other)) return false; + if (Object_hash(self) != Object_hash(other)) + return false; - switch((objectType)self->type) { + switch ((objectType)self->type) { case stringType: return String_equals((String *)self, (String *)other); break; @@ -304,16 +357,20 @@ inline bool Object_equals(Object *self, Object *other) { return Class_equals((Class *)self, (Class *)other); break; case persistentListType: - return PersistentList_equals((PersistentList *)self, (PersistentList *)other); - break; + return PersistentList_equals((PersistentList *)self, + (PersistentList *)other); + break; case persistentVectorType: - return PersistentVector_equals((PersistentVector *)self, (PersistentVector *)other); + return PersistentVector_equals((PersistentVector *)self, + (PersistentVector *)other); break; case persistentVectorNodeType: - return PersistentVectorNode_equals((PersistentVectorNode *)self, (PersistentVectorNode *)other); + return PersistentVectorNode_equals((PersistentVectorNode *)self, + (PersistentVectorNode *)other); break; case concurrentHashMapType: - return ConcurrentHashMap_equals((ConcurrentHashMap *)self, (ConcurrentHashMap *)other); + return ConcurrentHashMap_equals((ConcurrentHashMap *)self, + (ConcurrentHashMap *)other); break; case functionType: return Function_equals((ClojureFunction *)self, (ClojureFunction *)other); @@ -325,34 +382,38 @@ inline bool Object_equals(Object *self, Object *other) { return Ratio_equals((Ratio *)self, (Ratio *)other); break; case persistentArrayMapType: - return PersistentArrayMap_equals((PersistentArrayMap *)self, (PersistentArrayMap *)other); + return PersistentArrayMap_equals((PersistentArrayMap *)self, + (PersistentArrayMap *)other); break; default: - assert(false && "Internal error: hash computation for NaN tagged types should be computed earlier."); + assert(false && "Internal error: hash computation for NaN tagged types " + "should be computed earlier."); } } /* Outside of refcount system - should it be like this? It probably shouldnt */ inline bool equals(RTValue self, RTValue other) { - if (self == other) return true; // Handles same-ints, same-bools, and same-pointers - + if (self == other) + return true; // Handles same-ints, same-bools, and same-pointers + objectType t1 = getType(self); objectType t2 = getType(other); - if (t1 != t2) return false; - + if (t1 != t2) + return false; + if (RT_isPtr(self)) { - return Object_equals((Object*)RT_unboxPtr(self), (Object*)RT_unboxPtr(other)); + return Object_equals((Object *)RT_unboxPtr(self), + (Object *)RT_unboxPtr(other)); } - + if (t1 == doubleType) { return RT_unboxDouble(self) == RT_unboxDouble(other); } - return false; + return false; } - inline String *Object_toString(Object *restrict self) { - switch((objectType)self->type) { + switch ((objectType)self->type) { case stringType: return String_toString((String *)self); break; @@ -361,7 +422,7 @@ inline String *Object_toString(Object *restrict self) { break; case persistentListType: return PersistentList_toString((PersistentList *)self); - break; + break; case persistentVectorType: return PersistentVector_toString((PersistentVector *)self); break; @@ -384,15 +445,21 @@ inline String *Object_toString(Object *restrict self) { } inline String *toString(RTValue self) { - if (RT_isInt32(self)) return Integer_toString(self); - if (RT_isDouble(self)) return Double_toString(self); - if (RT_isBool(self)) return Boolean_toString(self); - if (RT_isNil(self)) return Nil_toString(); - if (RT_isKeyword(self)) return Keyword_toString(self); - if (RT_isSymbol(self)) return Symbol_toString(self); + if (RT_isInt32(self)) + return Integer_toString(self); + if (RT_isDouble(self)) + return Double_toString(self); + if (RT_isBool(self)) + return Boolean_toString(self); + if (RT_isNil(self)) + return Nil_toString(); + if (RT_isKeyword(self)) + return Keyword_toString(self); + if (RT_isSymbol(self)) + return Symbol_toString(self); assert(RT_isPtr(self) && "Internal error: Not a pointer"); - return Object_toString((Object*)RT_unboxPtr(self)); + return Object_toString((Object *)RT_unboxPtr(self)); } inline bool Object_release(Object *restrict self) { @@ -400,11 +467,14 @@ inline bool Object_release(Object *restrict self) { } inline void Object_autorelease(Object *restrict self) { - /* The object could have been deallocated through direct releases in the meantime (e.g. if autoreleasing entity does not own ) */ + /* The object could have been deallocated through direct releases in the + * meantime (e.g. if autoreleasing entity does not own ) */ #ifdef REFCOUNT_NONATOMIC - if(self->refCount < 1) return; + if (self->refCount < 1) + return; #else - if(atomic_load(&(self->atomicRefCount)) < 1) return; + if (atomic_load(&(self->atomicRefCount)) < 1) + return; #endif /* TODO: add an object to autorelease pool */ /* If we have no other threads working, we release immediately */ @@ -413,20 +483,20 @@ inline void Object_autorelease(Object *restrict self) { inline void autorelease(RTValue self) { if (RT_isPtr(self)) { - Object_autorelease((Object*)RT_unboxPtr(self)); - } + Object_autorelease((Object *)RT_unboxPtr(self)); + } } inline void retain(RTValue self) { if (RT_isPtr(self)) { - Object_retain((Object*)RT_unboxPtr(self)); - } + Object_retain((Object *)RT_unboxPtr(self)); + } } inline bool release(RTValue self) { if (RT_isPtr(self)) { - return Object_release((Object*)RT_unboxPtr(self)); - } + return Object_release((Object *)RT_unboxPtr(self)); + } return false; } @@ -434,17 +504,18 @@ inline void Object_create(Object *restrict self, objectType type) { #ifdef REFCOUNT_NONATOMIC self->refCount = 1; #else - atomic_store_explicit (&(self->atomicRefCount), 1, memory_order_relaxed); + atomic_store_explicit(&(self->atomicRefCount), 1, memory_order_relaxed); #endif self->type = type; #ifdef REFCOUNT_TRACING - atomic_fetch_add_explicit(&(allocationCount[self->type-1]), 1, memory_order_relaxed); - atomic_fetch_add_explicit(&(objectCount[self->type-1]), 1, memory_order_relaxed); + atomic_fetch_add_explicit(&(allocationCount[self->type - 1]), 1, + memory_order_relaxed); + atomic_fetch_add_explicit(&(objectCount[self->type - 1]), 1, + memory_order_relaxed); #endif -// printf("--> Allocating type %d addres %p\n", self->type, ); + // printf("--> Allocating type %d addres %p\n", self->type, ); } - inline uword_t combineHash(uword_t lhs, uword_t rhs) { lhs ^= rhs + 0x9ddfea08eb382d69ULL + (lhs << 6) + (lhs >> 2); return lhs; @@ -453,15 +524,16 @@ inline uword_t combineHash(uword_t lhs, uword_t rhs) { inline void Ptr_autorelease(void *self) { Object_autorelease((Object *)self); } inline void Ptr_retain(void *self) { Object_retain((Object *)self); } inline bool Ptr_release(void *self) { return Object_release((Object *)self); } -inline uword_t Ptr_hash(void *self) { return Object_hash((Object*)self); } -inline bool Ptr_isReusable(void *self) { return Object_isReusable((Object *)self); } +inline uword_t Ptr_hash(void *self) { return Object_hash((Object *)self); } +inline bool Ptr_isReusable(void *self) { + return Object_isReusable((Object *)self); +} inline bool Ptr_equals(void *self, void *other) { - if (self == other) return true; + if (self == other) + return true; return Object_equals((Object *)self, (Object *)other); } - - #pragma clang diagnostic pop #endif diff --git a/backend-v2/runtime/ObjectProto.h b/backend-v2/runtime/ObjectProto.h index 722597d4..bce5ca01 100644 --- a/backend-v2/runtime/ObjectProto.h +++ b/backend-v2/runtime/ObjectProto.h @@ -1,16 +1,33 @@ #ifndef OBJECT_PROTO_H #define OBJECT_PROTO_H +#ifdef __cplusplus +#include +using std::atomic_compare_exchange_strong_explicit; +using std::atomic_compare_exchange_weak_explicit; +using std::atomic_exchange_explicit; +using std::atomic_fetch_add_explicit; +using std::atomic_fetch_sub_explicit; +using std::atomic_load_explicit; +using std::atomic_store_explicit; +using std::memory_order; +using std::memory_order_acq_rel; +using std::memory_order_acquire; +using std::memory_order_relaxed; +using std::memory_order_release; +using std::memory_order_seq_cst; +#define _Atomic(X) std::atomic +#else #include +#endif #include "word.h" enum objectType { integerType = 1, - doubleType, // 2 - nilType, // 3 + doubleType, // 2 + nilType, // 3 booleanType, // 4 symbolType, // 5 keywordType, // 6 - stringType, // 7 persistentListType, // 8 persistentVectorType, // 9 @@ -29,12 +46,12 @@ struct Object { #ifdef REFCOUNT_NONATOMIC uword_t refCount; #endif - _Atomic uword_t atomicRefCount; + _Atomic(uword_t) atomicRefCount; objectType type; #ifdef USE_MEMORY_BANKS unsigned char bankId; #endif }; -typedef struct Object Object; +typedef struct Object Object; #endif diff --git a/backend-v2/runtime/Ratio.c b/backend-v2/runtime/Ratio.c index 4d1cae85..6d265990 100644 --- a/backend-v2/runtime/Ratio.c +++ b/backend-v2/runtime/Ratio.c @@ -89,7 +89,9 @@ uword_t Ratio_hash(Ratio *self) { /* mem done */ String *Ratio_toString(Ratio *self) { // Do not use BigInteger_toString! Numerator and denominator do not display N - String *num = String_create(mpq_get_str(NULL, 10, self->value)); + char *str = mpq_get_str(NULL, 10, self->value); + String *num = String_createDynamicStr(str); + free(str); Ptr_release(self); return num; } diff --git a/backend-v2/runtime/RuntimeInterface.c b/backend-v2/runtime/RuntimeInterface.c index 4ebdb1f4..2135c9d6 100644 --- a/backend-v2/runtime/RuntimeInterface.c +++ b/backend-v2/runtime/RuntimeInterface.c @@ -1,7 +1,7 @@ #include "RuntimeInterface.h" #include "ConcurrentHashMap.h" -#include #include +#include ConcurrentHashMap *keywords = NULL; ConcurrentHashMap *keywordsInverted = NULL; @@ -20,44 +20,65 @@ extern void logText(const char *text); extern void printReferenceCounts(); extern uint64_t avalanche_64(uint64_t h); - void RuntimeInterface_initialise() { - keywords = ConcurrentHashMap_create(10); // 2^10 - keywordsInverted = ConcurrentHashMap_create(10); // 2^10 - vars = ConcurrentHashMap_create(10); // 2^10 + keywords = ConcurrentHashMap_create(10); // 2^10 + keywordsInverted = ConcurrentHashMap_create(10); // 2^10 + vars = ConcurrentHashMap_create(10); // 2^10 symbols = ConcurrentHashMap_create(10); - symbolsInverted = ConcurrentHashMap_create(10); + symbolsInverted = ConcurrentHashMap_create(10); +} + +void RuntimeInterface_cleanup() { + if (keywords) { + Ptr_release(keywords); + keywords = NULL; + } + if (keywordsInverted) { + Ptr_release(keywordsInverted); + keywordsInverted = NULL; + } + if (symbols) { + Ptr_release(symbols); + symbols = NULL; + } + if (symbolsInverted) { + Ptr_release(symbolsInverted); + symbolsInverted = NULL; + } + if (vars) { + Ptr_release(vars); + vars = NULL; + } } void printReferenceCounts() { - printf("Ref counters: "); - for(unsigned char i= integerType; i <= persistentArrayMapType; i++) { - printf("%lu/%lu ", allocationCount[i-1], objectCount[i-1]); + printf("Ref counters: "); + for (unsigned char i = integerType; i <= persistentArrayMapType; i++) { + printf("%lu/%lu ", allocationCount[i - 1], objectCount[i - 1]); } printf("\n"); } - void logBacktrace() { - void *array[1000]; + void *array[1000]; char **strings; int size, i; - size = backtrace (array, 1000); - strings = backtrace_symbols (array, size); - if (strings != NULL) - { + size = backtrace(array, 1000); + strings = backtrace_symbols(array, size); + if (strings != NULL) { - printf ("Obtained %d stack frames:\n", size); + printf("Obtained %d stack frames:\n", size); for (i = 0; i < size; i++) - printf ("%s\n", strings[i]); + printf("%s\n", strings[i]); } - free (strings); + free(strings); } void **packPointerArgs(uword_t count, ...) { - if (!count) return NULL; + if (!count) + return NULL; void **ptr = allocate(sizeof(void *) * count); va_list args; va_start(args, count); @@ -69,7 +90,8 @@ void **packPointerArgs(uword_t count, ...) { } RTValue *packValueArgs(uword_t count, ...) { - if (!count) return NULL; + if (!count) + return NULL; RTValue *ptr = allocate(sizeof(RTValue) * count); va_list args; va_start(args, count); @@ -79,4 +101,3 @@ RTValue *packValueArgs(uword_t count, ...) { va_end(args); return ptr; } - diff --git a/backend-v2/runtime/RuntimeInterface.h b/backend-v2/runtime/RuntimeInterface.h index 1e298e4d..d6bf03a2 100644 --- a/backend-v2/runtime/RuntimeInterface.h +++ b/backend-v2/runtime/RuntimeInterface.h @@ -4,11 +4,13 @@ #include "RTValue.h" void RuntimeInterface_initialise(); - +void RuntimeInterface_cleanup(); inline bool logicalValue(RTValue self) { - if(RT_isNil(self)) return false; - if(RT_isBool(self)) return RT_unboxBool(self); + if (RT_isNil(self)) + return false; + if (RT_isBool(self)) + return RT_unboxBool(self); return true; } @@ -18,27 +20,26 @@ inline void logException(const char *description) { exit(1); } -inline void logType(const objectType ll) { - printf("Type: %d\n", ll); -} +inline void logType(const objectType ll) { printf("Type: %d\n", ll); } -inline void logText(const char *text) { - printf("Log: %s\n", text); -} +inline void logText(const char *text) { printf("Log: %s\n", text); } inline bool unboxedEqualsInteger(RTValue left, int32_t right) { - if(!RT_isInt32(left)) return false; + if (!RT_isInt32(left)) + return false; return RT_unboxInt32(left) == right; } inline bool unboxedEqualsDouble(RTValue left, double right) { - if(!RT_isDouble(left)) return false; - return RT_unboxDouble(left) == right; + if (!RT_isDouble(left)) + return false; + return RT_unboxDouble(left) == right; } inline bool unboxedEqualsBoolean(RTValue left, bool right) { - if(!RT_isBool(left)) return false; - return RT_unboxBool(left) == right; + if (!RT_isBool(left)) + return false; + return RT_unboxBool(left) == right; } void printReferenceCounts(); @@ -47,4 +48,3 @@ void **packPointerArgs(uword_t count, ...); RTValue *packValueArgs(uword_t count, ...); #endif - diff --git a/backend-v2/runtime/Symbol.c b/backend-v2/runtime/Symbol.c index bea64609..1d7452a3 100644 --- a/backend-v2/runtime/Symbol.c +++ b/backend-v2/runtime/Symbol.c @@ -9,37 +9,37 @@ extern ConcurrentHashMap *symbols; extern ConcurrentHashMap *symbolsInverted; static pthread_mutex_t intern_mutex = PTHREAD_MUTEX_INITIALIZER; -static _Atomic uint32_t minUnusedSymbol = 1; +static _Atomic(uint32_t) minUnusedSymbol = 1; -/* mem done */ +/* mem done - consumes string */ RTValue Symbol_create(String *string) { RTValue stringVal = RT_boxPtr(string); - Ptr_retain(string); - RTValue retVal = ConcurrentHashMap_get(symbols, stringVal); + Ptr_retain(string); /* +1 for first get */ + RTValue retVal = ConcurrentHashMap_get(symbols, stringVal); /* consumes 1 */ if (RT_isNil(retVal)) { - Ptr_retain(string); + Ptr_retain(string); /* +1 for second get */ pthread_mutex_lock(&intern_mutex); - /* interning */ - RTValue retVal2 = ConcurrentHashMap_get(symbols, stringVal); + RTValue retVal2 = + ConcurrentHashMap_get(symbols, stringVal); /* consumes 1 */ if (RT_isSymbol(retVal2)) { pthread_mutex_unlock(&intern_mutex); - Ptr_release(string); + Ptr_release(string); /* consume caller's ref */ return retVal2; } RTValue new = RT_boxSymbol( atomic_fetch_add_explicit(&minUnusedSymbol, 1, memory_order_relaxed)); + /* Need 2 refs for 2 assoc calls (each consumes stringVal as key/value). + We currently hold 1 (caller's), so retain once more. */ Ptr_retain(string); - ConcurrentHashMap_assoc(symbolsInverted, new, stringVal); - Ptr_retain(string); - ConcurrentHashMap_assoc(symbols, stringVal, new); + ConcurrentHashMap_assoc(symbolsInverted, new, stringVal); /* consumes 1 */ + ConcurrentHashMap_assoc(symbols, stringVal, new); /* consumes 1 */ pthread_mutex_unlock(&intern_mutex); - Ptr_release(string); return new; } - Ptr_release(string); + Ptr_release(string); /* consume caller's ref */ return retVal; } diff --git a/backend-v2/runtime/Transient.h b/backend-v2/runtime/Transient.h index 312926ec..1604253c 100644 --- a/backend-v2/runtime/Transient.h +++ b/backend-v2/runtime/Transient.h @@ -1,7 +1,25 @@ #ifndef RT_TRANSIENT #define RT_TRANSIENT +#ifdef __cplusplus +#include +using std::atomic_compare_exchange_strong_explicit; +using std::atomic_compare_exchange_weak_explicit; +using std::atomic_exchange_explicit; +using std::atomic_fetch_add_explicit; +using std::atomic_fetch_sub_explicit; +using std::atomic_load_explicit; +using std::atomic_store_explicit; +using std::memory_order; +using std::memory_order_acq_rel; +using std::memory_order_acquire; +using std::memory_order_relaxed; +using std::memory_order_release; +using std::memory_order_seq_cst; +#define _Atomic(X) std::atomic +#else #include +#endif #define PERSISTENT 0 diff --git a/backend-v2/runtime/tests/Keyword_test.c b/backend-v2/runtime/tests/Keyword_test.c index 8aca76b1..394ee97c 100644 --- a/backend-v2/runtime/tests/Keyword_test.c +++ b/backend-v2/runtime/tests/Keyword_test.c @@ -32,5 +32,7 @@ int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_keyword_interning), }; - return cmocka_run_group_tests(tests, NULL, NULL); + int result = cmocka_run_group_tests(tests, NULL, NULL); + RuntimeInterface_cleanup(); + return result; } diff --git a/backend-v2/runtime/tests/PersistentVector_test.c b/backend-v2/runtime/tests/PersistentVector_test.c index fc5dd3c4..435b3624 100644 --- a/backend-v2/runtime/tests/PersistentVector_test.c +++ b/backend-v2/runtime/tests/PersistentVector_test.c @@ -178,6 +178,7 @@ static void vectorAssocUpdate(void **state) { Ptr_retain(v); // Update index i to value 7 PersistentVector *next = PersistentVector_assoc(v, i, RT_boxInt32(7)); + Ptr_release(v); // Release the old reference v = next; } timerStop(&timer); diff --git a/backend-v2/runtime/tests/Symbol_test.c b/backend-v2/runtime/tests/Symbol_test.c index 01ffc85e..06e0b29a 100644 --- a/backend-v2/runtime/tests/Symbol_test.c +++ b/backend-v2/runtime/tests/Symbol_test.c @@ -23,8 +23,7 @@ static void test_symbol_interning(void **state) { PersistentVector *v = PersistentVector_create(); v = PersistentVector_conj(v, sym); - release(RT_boxPtr(v)); - release(sym); + Ptr_release(v); }); } @@ -33,5 +32,7 @@ int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_symbol_interning), }; - return cmocka_run_group_tests(tests, NULL, NULL); + int result = cmocka_run_group_tests(tests, NULL, NULL); + RuntimeInterface_cleanup(); + return result; } diff --git a/backend-v2/runtime/tests/TestTools.h b/backend-v2/runtime/tests/TestTools.h index 9ea4ecf1..49c9f1f8 100644 --- a/backend-v2/runtime/tests/TestTools.h +++ b/backend-v2/runtime/tests/TestTools.h @@ -8,7 +8,25 @@ #include #include #include +#ifdef __cplusplus +#include +using std::atomic_compare_exchange_strong_explicit; +using std::atomic_compare_exchange_weak_explicit; +using std::atomic_exchange_explicit; +using std::atomic_fetch_add_explicit; +using std::atomic_fetch_sub_explicit; +using std::atomic_load_explicit; +using std::atomic_store_explicit; +using std::memory_order; +using std::memory_order_acq_rel; +using std::memory_order_acquire; +using std::memory_order_relaxed; +using std::memory_order_release; +using std::memory_order_seq_cst; +#define _Atomic(X) std::atomic +#else #include +#endif #include #include #include @@ -55,7 +73,14 @@ void testScalingBehavior(void **state); #define ASSERT_MEMORY_BALANCE(type, block) \ do { \ - uword_t start_count = allocationCount[(type)-1]; \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wstringop-overflow\"") if ( \ + strstr(BUILD_TYPE, "Release")) { \ + fail_msg("ASSERT_MEMORY_BALANCE cannot be used in Release mode as " \ + "memory tracking results may be unreliable or disabled."); \ + } \ + _Pragma("GCC diagnostic pop") uword_t start_count = \ + allocationCount[(type)-1]; \ block uword_t end_count = allocationCount[(type)-1]; \ assert_int_equal(start_count, end_count); \ } while (0) @@ -78,6 +103,10 @@ void assertMemoryDifference(MemoryState *before, MemoryState *after, int type, // type #define ASSERT_MEMORY_ALL_BALANCED(block) \ do { \ + if (strstr(BUILD_TYPE, "Release")) { \ + fail_msg("ASSERT_MEMORY_ALL_BALANCED cannot be used in Release mode as " \ + "memory tracking results may be unreliable or disabled."); \ + } \ MemoryState __before, __after; \ captureMemoryState(&__before); \ {block} captureMemoryState(&__after); \ diff --git a/backend-v2/tests/CMakeLists.txt b/backend-v2/tests/CMakeLists.txt index cdb8badb..ed5a3f6d 100644 --- a/backend-v2/tests/CMakeLists.txt +++ b/backend-v2/tests/CMakeLists.txt @@ -3,16 +3,20 @@ set(COMMON_TEST_SOURCES ) set(TEST_MODULES - EdnParser_test - EdnParser_functional_test - EdnParser_negative_test + tools/EdnParser_test ) foreach(TEST_MODULE ${TEST_MODULES}) - set(EXEC_NAME backend_${TEST_MODULE}) + string(REPLACE "/" "_" SAFE_TEST_MODULE ${TEST_MODULE}) + set(EXEC_NAME backend_${SAFE_TEST_MODULE}) add_executable(${EXEC_NAME} ${TEST_MODULE}.cpp ${COMMON_TEST_SOURCES}) target_link_libraries(${EXEC_NAME} PUBLIC backend_lib runtime ${CMOCKA_LIBRARIES} ${GMP_LIBRARY}) + # On Linux, export symbols so the JIT can find runtime functions + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_options(${EXEC_NAME} PRIVATE -rdynamic) + endif() + add_test( NAME ${EXEC_NAME} COMMAND ${EXEC_NAME} diff --git a/backend-v2/tests/EdnParser_functional_test.cpp b/backend-v2/tests/EdnParser_functional_test.cpp deleted file mode 100644 index c7bbfcbe..00000000 --- a/backend-v2/tests/EdnParser_functional_test.cpp +++ /dev/null @@ -1,245 +0,0 @@ -#include -#include -#include - -#include "../RuntimeHeaders.h" -#include "../tools/EdnParser.h" - -extern "C" { -#include -#include -#include -#include -#include - -#include "../runtime/Keyword.h" -#include "../runtime/PersistentArrayMap.h" -#include "../runtime/PersistentVector.h" -#include "../runtime/RuntimeInterface.h" -#include "../runtime/String.h" -#include "../runtime/Symbol.h" -#include "../runtime/tests/TestTools.h" -} - -using namespace std; -using namespace rt; - -static void run_test(void (*test_func)(void **), void **state, - const char *name) { - try { - test_func(state); - } catch (const LanguageException &e) { - llvm::symbolize::LLVMSymbolizer::Options options; - llvm::symbolize::LLVMSymbolizer symbolizer(options); - fprintf(stderr, "Test %s failed with LanguageException:\n%s\n", name, - e.toString(symbolizer).c_str()); - assert_true(false); - } catch (const std::exception &e) { - fprintf(stderr, "Test %s failed with std::exception: %s\n", name, e.what()); - assert_true(false); - } catch (...) { - fprintf(stderr, "Test %s failed with unknown exception\n", name); - assert_true(false); - } -} - -static void test_class_aliasing_impl(void **state) { - (void)state; - ASSERT_MEMORY_ALL_BALANCED({ - // Define Class A with alias :a - // {com.foo/A {:alias :a, :object-type 100}} - PersistentArrayMap *classAMap = PersistentArrayMap_empty(); - classAMap = PersistentArrayMap_assoc(classAMap, - Keyword_create(String_create("alias")), - Keyword_create(String_create("a"))); - classAMap = PersistentArrayMap_assoc( - classAMap, Keyword_create(String_create("object-type")), - RT_boxInt32(100)); - - // Define Class B that uses alias :a - // {com.foo/B {:instance-fns {msg [{:args [:a], :type :call, :symbol - // "msg_sym"}]}}} - PersistentArrayMap *msgMap = PersistentArrayMap_empty(); - PersistentVector *argsVec = PersistentVector_create(); - argsVec = - PersistentVector_conj(argsVec, Keyword_create(String_create("a"))); - msgMap = PersistentArrayMap_assoc( - msgMap, Keyword_create(String_create("args")), RT_boxPtr(argsVec)); - msgMap = - PersistentArrayMap_assoc(msgMap, Keyword_create(String_create("type")), - Keyword_create(String_create("call"))); - msgMap = PersistentArrayMap_assoc(msgMap, - Keyword_create(String_create("symbol")), - RT_boxPtr(String_create("msg_sym"))); - - PersistentVector *msgOverloads = PersistentVector_create(); - msgOverloads = PersistentVector_conj(msgOverloads, RT_boxPtr(msgMap)); - - PersistentArrayMap *instanceFnsMap = PersistentArrayMap_empty(); - instanceFnsMap = PersistentArrayMap_assoc( - instanceFnsMap, Symbol_create(String_create("msg")), - RT_boxPtr(msgOverloads)); - - PersistentArrayMap *classBMap = PersistentArrayMap_empty(); - classBMap = PersistentArrayMap_assoc( - classBMap, Keyword_create(String_create("instance-fns")), - RT_boxPtr(instanceFnsMap)); - - // Root map: {com.foo/A classAMap, com.foo/B classBMap} - PersistentArrayMap *rootMap = PersistentArrayMap_empty(); - rootMap = PersistentArrayMap_assoc( - rootMap, Symbol_create(String_create("com.foo/A")), - RT_boxPtr(classAMap)); - rootMap = PersistentArrayMap_assoc( - rootMap, Symbol_create(String_create("com.foo/B")), - RT_boxPtr(classBMap)); - - vector classes = buildClasses(RT_boxPtr(rootMap)); - - assert_int_equal(2, classes.size()); - - ClassDescription *descA = nullptr; - ClassDescription *descB = nullptr; - for (auto &c : classes) { - if (c.name == "com.foo/A") - descA = &c; - if (c.name == "com.foo/B") - descB = &c; - } - - assert_non_null(descA); - assert_non_null(descB); - - assert_true(descA->type.isDetermined()); - assert_int_equal(100, (int)descA->type.determinedType()); - - // Check B's intrinsic args - auto it = descB->instanceFns.find("msg"); - assert_true(it != descB->instanceFns.end()); - assert_int_equal(1, it->second.size()); - assert_int_equal(1, it->second[0].argTypes.size()); - assert_true(it->second[0].argTypes[0].isDetermined()); - assert_int_equal(100, (int)it->second[0].argTypes[0].determinedType()); - }); -} - -static void test_class_aliasing(void **state) { - run_test(test_class_aliasing_impl, state, "test_class_aliasing"); -} - -static void test_static_fields_impl(void **state) { - (void)state; - ASSERT_MEMORY_ALL_BALANCED({ - // {com.foo/MyClass {:static-fields {VERSION "1.0", COUNT 42}}} - PersistentArrayMap *fieldsMap = PersistentArrayMap_empty(); - fieldsMap = PersistentArrayMap_assoc( - fieldsMap, Symbol_create(String_create("VERSION")), - RT_boxPtr(String_create("1.0"))); - fieldsMap = PersistentArrayMap_assoc( - fieldsMap, Symbol_create(String_create("COUNT")), RT_boxInt32(42)); - - PersistentArrayMap *classMap = PersistentArrayMap_empty(); - classMap = PersistentArrayMap_assoc( - classMap, Keyword_create(String_create("static-fields")), - RT_boxPtr(fieldsMap)); - - PersistentArrayMap *rootMap = PersistentArrayMap_empty(); - rootMap = PersistentArrayMap_assoc( - rootMap, Symbol_create(String_create("com.foo/MyClass")), - RT_boxPtr(classMap)); - - vector classes = buildClasses(RT_boxPtr(rootMap)); - assert_int_equal(1, classes.size()); - - auto &c = classes[0]; - assert_int_equal(2, c.staticFields.size()); - - auto itVersion = c.staticFields.find("VERSION"); - assert_true(itVersion != c.staticFields.end()); - assert_string_equal("1.0", - String_c_str((String *)RT_unboxPtr(itVersion->second))); - - auto itCount = c.staticFields.find("COUNT"); - assert_true(itCount != c.staticFields.end()); - assert_int_equal(42, RT_unboxInt32(itCount->second)); - }); -} - -static void test_static_fields(void **state) { - run_test(test_static_fields_impl, state, "test_static_fields"); -} - -static void test_special_types_impl(void **state) { - (void)state; - ASSERT_MEMORY_ALL_BALANCED({ - // {com.foo/A {:instance-fns {f [{:args [:this :any :nil], :returns :nil, - // :type :call, :symbol "f_sym"}]}}} - PersistentVector *argsVec = PersistentVector_create(); - argsVec = - PersistentVector_conj(argsVec, Keyword_create(String_create("this"))); - argsVec = - PersistentVector_conj(argsVec, Keyword_create(String_create("any"))); - argsVec = - PersistentVector_conj(argsVec, Keyword_create(String_create("nil"))); - - PersistentArrayMap *fMap = PersistentArrayMap_empty(); - fMap = PersistentArrayMap_assoc(fMap, Keyword_create(String_create("args")), - RT_boxPtr(argsVec)); - fMap = - PersistentArrayMap_assoc(fMap, Keyword_create(String_create("returns")), - Keyword_create(String_create("nil"))); - fMap = PersistentArrayMap_assoc(fMap, Keyword_create(String_create("type")), - Keyword_create(String_create("call"))); - fMap = - PersistentArrayMap_assoc(fMap, Keyword_create(String_create("symbol")), - RT_boxPtr(String_create("f_sym"))); - - PersistentVector *fOverloads = PersistentVector_create(); - fOverloads = PersistentVector_conj(fOverloads, RT_boxPtr(fMap)); - - PersistentArrayMap *instanceFnsMap = PersistentArrayMap_empty(); - instanceFnsMap = PersistentArrayMap_assoc(instanceFnsMap, - Symbol_create(String_create("f")), - RT_boxPtr(fOverloads)); - - PersistentArrayMap *classAMap = PersistentArrayMap_empty(); - classAMap = PersistentArrayMap_assoc( - classAMap, Keyword_create(String_create("instance-fns")), - RT_boxPtr(instanceFnsMap)); - - PersistentArrayMap *rootMap = PersistentArrayMap_empty(); - rootMap = PersistentArrayMap_assoc( - rootMap, Symbol_create(String_create("com.foo/A")), - RT_boxPtr(classAMap)); - - vector classes = buildClasses(RT_boxPtr(rootMap)); - assert_int_equal(1, classes.size()); - - auto &c = classes[0]; - auto it = c.instanceFns.find("f"); - assert_true(it != c.instanceFns.end()); - auto &intrinsic = it->second[0]; - - assert_int_equal(3, intrinsic.argTypes.size()); - assert_true(intrinsic.argTypes[0].contains(classType)); - assert_true(intrinsic.argTypes[1].isDynamic()); - assert_true(intrinsic.argTypes[2].contains(nilType)); - assert_true(intrinsic.returnType.contains(nilType)); - }); -} - -static void test_special_types(void **state) { - run_test(test_special_types_impl, state, "test_special_types"); -} - -int main(void) { - initialise_memory(); - // Warm up the static empty map to avoid false leak report in first test - PersistentArrayMap_empty(); - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_class_aliasing), - cmocka_unit_test(test_static_fields), - cmocka_unit_test(test_special_types), - }; - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/backend-v2/tests/EdnParser_test.cpp b/backend-v2/tests/EdnParser_test.cpp deleted file mode 100644 index 60b8b074..00000000 --- a/backend-v2/tests/EdnParser_test.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include -#include - -#include "../RuntimeHeaders.h" -#include "../bytecode.pb.h" -#include "../jit/JITEngine.h" -#include "../state/ThreadsafeCompilerState.h" - -extern "C" { -#include -#include -#include -#include -#include - -#include "../runtime/Keyword.h" -#include "../runtime/PersistentVector.h" -#include "../runtime/RuntimeInterface.h" -#include "../runtime/String.h" -#include "../runtime/tests/TestTools.h" -} - -#include "../tools/EdnParser.h" - -#include -std::string getSelfExecutablePath() { - char path[1024]; - uint32_t size = sizeof(path); - if (_NSGetExecutablePath(path, &size) == 0) - return std::string(path); - return ""; -} - -static void test_trivial_memory(void **state) { - (void)state; // unused - - ASSERT_MEMORY_ALL_BALANCED({ - RTValue val = RT_boxNil(); - release(val); - }); -} - -static void test_edn_parser_memory(void **state) { - (void)state; // unused - - // Ensure libprotobuf matches installed headers - GOOGLE_PROTOBUF_VERIFY_VERSION; - - clojure::rt::protobuf::bytecode::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."); - } - } - - ASSERT_MEMORY_ALL_BALANCED({ - rt::ThreadsafeCompilerState compState; - rt::JITEngine engine(compState); - - llvm::orc::ExecutorAddr res = - engine - .compileAST(astClasses.nodes(0), "__classes", - llvm::OptimizationLevel::O0, false) - .get(); - - RTValue classes = res.toPtr()(); - - // Release the final map holding the entire structure - release(classes); - }); -} - -static void test_edn_parser_class_parsing_memory(void **state) { - (void)state; // unused - - clojure::rt::protobuf::bytecode::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."); - } - } - - ASSERT_MEMORY_ALL_BALANCED({ - rt::ThreadsafeCompilerState compState; - rt::JITEngine engine(compState); - - llvm::orc::ExecutorAddr res = - engine - .compileAST(astClasses.nodes(0), "__classes", - llvm::OptimizationLevel::O0, false) - .get(); - - RTValue classes = res.toPtr()(); - - { - try { - vector classesList = rt::buildClasses(classes); - assert_true(classesList.size() > 0); - } catch (const rt::LanguageException &e) { - llvm::symbolize::LLVMSymbolizer::Options options; - options.Demangle = true; - options.PrintFunctions = llvm::symbolize::FunctionNameKind::LinkageName; - llvm::symbolize::LLVMSymbolizer symbolizer(options); - cout << e.toString(symbolizer, - getSelfExecutablePath() + - ".dSYM/Contents/Resources/DWARF/" + "clojure-rt", - _dyld_get_image_vmaddr_slide(0)) - << endl; - assert_true(false); - } catch (...) { - printf("Unknown exception caught\n"); - assert_true(false); - } - } - }); -} - -int main(int argc, char **argv) { - initialise_memory(); - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_trivial_memory), - cmocka_unit_test(test_edn_parser_memory), - cmocka_unit_test(test_edn_parser_class_parsing_memory), - }; - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/backend-v2/tests/EdnParser_negative_test.cpp b/backend-v2/tests/tools/EdnParser_test.cpp similarity index 55% rename from backend-v2/tests/EdnParser_negative_test.cpp rename to backend-v2/tests/tools/EdnParser_test.cpp index decfd6cc..bafaa0db 100644 --- a/backend-v2/tests/EdnParser_negative_test.cpp +++ b/backend-v2/tests/tools/EdnParser_test.cpp @@ -1,31 +1,321 @@ +#include "../../RuntimeHeaders.h" +#include "../../jit/JITEngine.h" +#include "../../state/ThreadsafeCompilerState.h" +#include "../../tools/EdnParser.h" +#include "bytecode.pb.h" +#include #include #include #include -#include "../RuntimeHeaders.h" -#include "../tools/EdnParser.h" - extern "C" { +#include "../../runtime/Keyword.h" +#include "../../runtime/PersistentArrayMap.h" +#include "../../runtime/PersistentVector.h" +#include "../../runtime/RuntimeInterface.h" +#include "../../runtime/String.h" +#include "../../runtime/Symbol.h" +#include "../../runtime/tests/TestTools.h" #include #include #include #include #include - -#include "../runtime/Keyword.h" -#include "../runtime/PersistentArrayMap.h" -#include "../runtime/PersistentVector.h" -#include "../runtime/RuntimeInterface.h" -#include "../runtime/String.h" -#include "../runtime/Symbol.h" -#include "../runtime/tests/TestTools.h" } +#include "../../bridge/Exceptions.h" + using namespace std; using namespace rt; -static void run_negative_test(void (*test_func)(void **), void **state, - const char *name) { +static void test_trivial_memory(void **state) { + (void)state; // unused + + ASSERT_MEMORY_ALL_BALANCED({ + RTValue val = RT_boxNil(); + release(val); + }); +} + +static void test_edn_parser_memory(void **state) { + (void)state; // unused + + // Ensure libprotobuf matches installed headers + GOOGLE_PROTOBUF_VERIFY_VERSION; + + clojure::rt::protobuf::bytecode::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."); + } + } + + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + + llvm::orc::ExecutorAddr res = + engine + .compileAST(astClasses.nodes(0), "__classes", + llvm::OptimizationLevel::O0, false) + .get(); + + RTValue classes = res.toPtr()(); + + // Release the final map holding the entire structure + release(classes); + }); +} + +static void test_edn_parser_class_parsing_memory(void **state) { + (void)state; // unused + + clojure::rt::protobuf::bytecode::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."); + } + } + + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + + llvm::orc::ExecutorAddr res = + engine + .compileAST(astClasses.nodes(0), "__classes", + llvm::OptimizationLevel::O0, false) + .get(); + + RTValue classes = res.toPtr()(); + + { + try { + vector classesList = rt::buildClasses(classes); + assert_true(classesList.size() > 0); + } catch (const rt::LanguageException &e) { + cout << rt::getExceptionString(e) << endl; + assert_true(false); + } catch (...) { + printf("Unknown exception caught\n"); + assert_true(false); + } + } + }); +} + +static void execute_test(void (*test_func)(void **), void **state, + const char *name) { + try { + test_func(state); + } catch (const LanguageException &e) { + fprintf(stderr, "Test %s failed with LanguageException:\n%s\n", name, + rt::getExceptionString(e).c_str()); + assert_true(false); + } catch (const std::exception &e) { + fprintf(stderr, "Test %s failed with std::exception: %s\n", name, e.what()); + assert_true(false); + } catch (...) { + fprintf(stderr, "Test %s failed with unknown exception\n", name); + assert_true(false); + } +} + +static void test_class_aliasing_impl(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + // Define Class A with alias :a + // {com.foo/A {:alias :a, :object-type 100}} + PersistentArrayMap *classAMap = PersistentArrayMap_empty(); + classAMap = PersistentArrayMap_assoc(classAMap, + Keyword_create(String_create("alias")), + Keyword_create(String_create("a"))); + classAMap = PersistentArrayMap_assoc( + classAMap, Keyword_create(String_create("object-type")), + RT_boxInt32(100)); + + // Define Class B that uses alias :a + // {com.foo/B {:instance-fns {msg [{:args [:a], :type :call, :symbol + // "msg_sym"}]}}} + PersistentArrayMap *msgMap = PersistentArrayMap_empty(); + PersistentVector *argsVec = PersistentVector_create(); + argsVec = + PersistentVector_conj(argsVec, Keyword_create(String_create("a"))); + msgMap = PersistentArrayMap_assoc( + msgMap, Keyword_create(String_create("args")), RT_boxPtr(argsVec)); + msgMap = + PersistentArrayMap_assoc(msgMap, Keyword_create(String_create("type")), + Keyword_create(String_create("call"))); + msgMap = PersistentArrayMap_assoc(msgMap, + Keyword_create(String_create("symbol")), + RT_boxPtr(String_create("msg_sym"))); + + PersistentVector *msgOverloads = PersistentVector_create(); + msgOverloads = PersistentVector_conj(msgOverloads, RT_boxPtr(msgMap)); + + PersistentArrayMap *instanceFnsMap = PersistentArrayMap_empty(); + instanceFnsMap = PersistentArrayMap_assoc( + instanceFnsMap, Symbol_create(String_create("msg")), + RT_boxPtr(msgOverloads)); + + PersistentArrayMap *classBMap = PersistentArrayMap_empty(); + classBMap = PersistentArrayMap_assoc( + classBMap, Keyword_create(String_create("instance-fns")), + RT_boxPtr(instanceFnsMap)); + + // Root map: {com.foo/A classAMap, com.foo/B classBMap} + PersistentArrayMap *rootMap = PersistentArrayMap_empty(); + rootMap = PersistentArrayMap_assoc( + rootMap, Symbol_create(String_create("com.foo/A")), + RT_boxPtr(classAMap)); + rootMap = PersistentArrayMap_assoc( + rootMap, Symbol_create(String_create("com.foo/B")), + RT_boxPtr(classBMap)); + + vector classes = buildClasses(RT_boxPtr(rootMap)); + + assert_int_equal(2, classes.size()); + + ClassDescription *descA = nullptr; + ClassDescription *descB = nullptr; + for (auto &c : classes) { + if (c.name == "com.foo/A") + descA = &c; + if (c.name == "com.foo/B") + descB = &c; + } + + assert_non_null(descA); + assert_non_null(descB); + + assert_true(descA->type.isDetermined()); + assert_int_equal(100, (int)descA->type.determinedType()); + + // Check B's intrinsic args + auto it = descB->instanceFns.find("msg"); + assert_true(it != descB->instanceFns.end()); + assert_int_equal(1, it->second.size()); + assert_int_equal(1, it->second[0].argTypes.size()); + assert_true(it->second[0].argTypes[0].isDetermined()); + assert_int_equal(100, (int)it->second[0].argTypes[0].determinedType()); + }); +} + +static void test_class_aliasing(void **state) { + execute_test(test_class_aliasing_impl, state, "test_class_aliasing"); +} + +static void test_static_fields_impl(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + // {com.foo/MyClass {:static-fields {VERSION "1.0", COUNT 42}}} + PersistentArrayMap *fieldsMap = PersistentArrayMap_empty(); + fieldsMap = PersistentArrayMap_assoc( + fieldsMap, Symbol_create(String_create("VERSION")), + RT_boxPtr(String_create("1.0"))); + fieldsMap = PersistentArrayMap_assoc( + fieldsMap, Symbol_create(String_create("COUNT")), RT_boxInt32(42)); + + PersistentArrayMap *classMap = PersistentArrayMap_empty(); + classMap = PersistentArrayMap_assoc( + classMap, Keyword_create(String_create("static-fields")), + RT_boxPtr(fieldsMap)); + + PersistentArrayMap *rootMap = PersistentArrayMap_empty(); + rootMap = PersistentArrayMap_assoc( + rootMap, Symbol_create(String_create("com.foo/MyClass")), + RT_boxPtr(classMap)); + + vector classes = buildClasses(RT_boxPtr(rootMap)); + assert_int_equal(1, classes.size()); + + auto &c = classes[0]; + assert_int_equal(2, c.staticFields.size()); + + auto itVersion = c.staticFields.find("VERSION"); + assert_true(itVersion != c.staticFields.end()); + assert_string_equal("1.0", + String_c_str((String *)RT_unboxPtr(itVersion->second))); + + auto itCount = c.staticFields.find("COUNT"); + assert_true(itCount != c.staticFields.end()); + assert_int_equal(42, RT_unboxInt32(itCount->second)); + }); +} + +static void test_static_fields(void **state) { + execute_test(test_static_fields_impl, state, "test_static_fields"); +} + +static void test_special_types_impl(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + // {com.foo/A {:instance-fns {f [{:args [:this :any :nil], :returns :nil, + // :type :call, :symbol "f_sym"}]}}} + PersistentVector *argsVec = PersistentVector_create(); + argsVec = + PersistentVector_conj(argsVec, Keyword_create(String_create("this"))); + argsVec = + PersistentVector_conj(argsVec, Keyword_create(String_create("any"))); + argsVec = + PersistentVector_conj(argsVec, Keyword_create(String_create("nil"))); + + PersistentArrayMap *fMap = PersistentArrayMap_empty(); + fMap = PersistentArrayMap_assoc(fMap, Keyword_create(String_create("args")), + RT_boxPtr(argsVec)); + fMap = + PersistentArrayMap_assoc(fMap, Keyword_create(String_create("returns")), + Keyword_create(String_create("nil"))); + fMap = PersistentArrayMap_assoc(fMap, Keyword_create(String_create("type")), + Keyword_create(String_create("call"))); + fMap = + PersistentArrayMap_assoc(fMap, Keyword_create(String_create("symbol")), + RT_boxPtr(String_create("f_sym"))); + + PersistentVector *fOverloads = PersistentVector_create(); + fOverloads = PersistentVector_conj(fOverloads, RT_boxPtr(fMap)); + + PersistentArrayMap *instanceFnsMap = PersistentArrayMap_empty(); + instanceFnsMap = PersistentArrayMap_assoc(instanceFnsMap, + Symbol_create(String_create("f")), + RT_boxPtr(fOverloads)); + + PersistentArrayMap *classAMap = PersistentArrayMap_empty(); + classAMap = PersistentArrayMap_assoc( + classAMap, Keyword_create(String_create("instance-fns")), + RT_boxPtr(instanceFnsMap)); + + PersistentArrayMap *rootMap = PersistentArrayMap_empty(); + rootMap = PersistentArrayMap_assoc( + rootMap, Symbol_create(String_create("com.foo/A")), + RT_boxPtr(classAMap)); + + vector classes = buildClasses(RT_boxPtr(rootMap)); + assert_int_equal(1, classes.size()); + + auto &c = classes[0]; + auto it = c.instanceFns.find("f"); + assert_true(it != c.instanceFns.end()); + auto &intrinsic = it->second[0]; + + assert_int_equal(3, intrinsic.argTypes.size()); + assert_true(intrinsic.argTypes[0].contains(classType)); + assert_true(intrinsic.argTypes[1].isDynamic()); + assert_true(intrinsic.argTypes[2].contains(nilType)); + assert_true(intrinsic.returnType.contains(nilType)); + }); +} + +static void test_special_types(void **state) { + execute_test(test_special_types_impl, state, "test_special_types"); +} + +static void execute_negative_test(void (*test_func)(void **), void **state, + const char *name) { try { test_func(state); fprintf(stderr, "Test %s failed: Expected exception not thrown\n", name); @@ -46,7 +336,7 @@ static void test_root_not_map_impl(void **state) { } static void test_root_not_map(void **state) { - run_negative_test(test_root_not_map_impl, state, "test_root_not_map"); + execute_negative_test(test_root_not_map_impl, state, "test_root_not_map"); } static void test_class_key_not_symbol_impl(void **state) { @@ -60,8 +350,8 @@ static void test_class_key_not_symbol_impl(void **state) { } static void test_class_key_not_symbol(void **state) { - run_negative_test(test_class_key_not_symbol_impl, state, - "test_class_key_not_symbol"); + execute_negative_test(test_class_key_not_symbol_impl, state, + "test_class_key_not_symbol"); } static void test_class_value_not_map_impl(void **state) { @@ -75,8 +365,8 @@ static void test_class_value_not_map_impl(void **state) { } static void test_class_value_not_map(void **state) { - run_negative_test(test_class_value_not_map_impl, state, - "test_class_value_not_map"); + execute_negative_test(test_class_value_not_map_impl, state, + "test_class_value_not_map"); } static void test_object_type_not_int_impl(void **state) { @@ -94,8 +384,8 @@ static void test_object_type_not_int_impl(void **state) { } static void test_object_type_not_int(void **state) { - run_negative_test(test_object_type_not_int_impl, state, - "test_object_type_not_int"); + execute_negative_test(test_object_type_not_int_impl, state, + "test_object_type_not_int"); } static void test_alias_not_keyword_impl(void **state) { @@ -113,8 +403,8 @@ static void test_alias_not_keyword_impl(void **state) { } static void test_alias_not_keyword(void **state) { - run_negative_test(test_alias_not_keyword_impl, state, - "test_alias_not_keyword"); + execute_negative_test(test_alias_not_keyword_impl, state, + "test_alias_not_keyword"); } // --- Group 2: ClassDescription --- @@ -137,8 +427,8 @@ static void test_static_fields_not_map_impl(void **state) { } static void test_static_fields_not_map(void **state) { - run_negative_test(test_static_fields_not_map_impl, state, - "test_static_fields_not_map"); + execute_negative_test(test_static_fields_not_map_impl, state, + "test_static_fields_not_map"); } static void test_instance_fns_not_map_impl(void **state) { @@ -159,8 +449,8 @@ static void test_instance_fns_not_map_impl(void **state) { } static void test_instance_fns_not_map(void **state) { - run_negative_test(test_instance_fns_not_map_impl, state, - "test_instance_fns_not_map"); + execute_negative_test(test_instance_fns_not_map_impl, state, + "test_instance_fns_not_map"); } // --- Group 3: Intrinsic Collections --- @@ -186,8 +476,8 @@ static void test_static_field_key_not_symbol_impl(void **state) { } static void test_static_field_key_not_symbol(void **state) { - run_negative_test(test_static_field_key_not_symbol_impl, state, - "test_static_field_key_not_symbol"); + execute_negative_test(test_static_field_key_not_symbol_impl, state, + "test_static_field_key_not_symbol"); } static void test_intrinsic_key_not_symbol_impl(void **state) { @@ -211,8 +501,8 @@ static void test_intrinsic_key_not_symbol_impl(void **state) { } static void test_intrinsic_key_not_symbol(void **state) { - run_negative_test(test_intrinsic_key_not_symbol_impl, state, - "test_intrinsic_key_not_symbol"); + execute_negative_test(test_intrinsic_key_not_symbol_impl, state, + "test_intrinsic_key_not_symbol"); } static void test_intrinsic_value_not_vector_impl(void **state) { @@ -236,8 +526,8 @@ static void test_intrinsic_value_not_vector_impl(void **state) { } static void test_intrinsic_value_not_vector(void **state) { - run_negative_test(test_intrinsic_value_not_vector_impl, state, - "test_intrinsic_value_not_vector"); + execute_negative_test(test_intrinsic_value_not_vector_impl, state, + "test_intrinsic_value_not_vector"); } // --- Group 4: IntrinsicDescription --- @@ -270,8 +560,8 @@ static void test_intrinsic_type_not_keyword_impl(void **state) { } static void test_intrinsic_type_not_keyword(void **state) { - run_negative_test(test_intrinsic_type_not_keyword_impl, state, - "test_intrinsic_type_not_keyword"); + execute_negative_test(test_intrinsic_type_not_keyword_impl, state, + "test_intrinsic_type_not_keyword"); } static void test_intrinsic_type_invalid_impl(void **state) { @@ -302,8 +592,8 @@ static void test_intrinsic_type_invalid_impl(void **state) { } static void test_intrinsic_type_invalid(void **state) { - run_negative_test(test_intrinsic_type_invalid_impl, state, - "test_intrinsic_type_invalid"); + execute_negative_test(test_intrinsic_type_invalid_impl, state, + "test_intrinsic_type_invalid"); } static void test_intrinsic_symbol_not_string_impl(void **state) { @@ -337,8 +627,8 @@ static void test_intrinsic_symbol_not_string_impl(void **state) { } static void test_intrinsic_symbol_not_string(void **state) { - run_negative_test(test_intrinsic_symbol_not_string_impl, state, - "test_intrinsic_symbol_not_string"); + execute_negative_test(test_intrinsic_symbol_not_string_impl, state, + "test_intrinsic_symbol_not_string"); } static void test_intrinsic_args_not_vector_impl(void **state) { @@ -374,8 +664,8 @@ static void test_intrinsic_args_not_vector_impl(void **state) { } static void test_intrinsic_args_not_vector(void **state) { - run_negative_test(test_intrinsic_args_not_vector_impl, state, - "test_intrinsic_args_not_vector"); + execute_negative_test(test_intrinsic_args_not_vector_impl, state, + "test_intrinsic_args_not_vector"); } static void test_unknown_arg_type_impl(void **state) { @@ -416,7 +706,8 @@ static void test_unknown_arg_type_impl(void **state) { } static void test_unknown_arg_type(void **state) { - run_negative_test(test_unknown_arg_type_impl, state, "test_unknown_arg_type"); + execute_negative_test(test_unknown_arg_type_impl, state, + "test_unknown_arg_type"); } static void test_unknown_return_type_impl(void **state) { @@ -453,14 +744,20 @@ static void test_unknown_return_type_impl(void **state) { } static void test_unknown_return_type(void **state) { - run_negative_test(test_unknown_return_type_impl, state, - "test_unknown_return_type"); + execute_negative_test(test_unknown_return_type_impl, state, + "test_unknown_return_type"); } -int main(void) { +int main(int argc, char **argv) { initialise_memory(); PersistentArrayMap_empty(); const struct CMUnitTest tests[] = { + cmocka_unit_test(test_trivial_memory), + cmocka_unit_test(test_edn_parser_memory), + cmocka_unit_test(test_edn_parser_class_parsing_memory), + cmocka_unit_test(test_class_aliasing), + cmocka_unit_test(test_static_fields), + cmocka_unit_test(test_special_types), cmocka_unit_test(test_root_not_map), cmocka_unit_test(test_class_key_not_symbol), cmocka_unit_test(test_class_value_not_map), @@ -478,5 +775,7 @@ int main(void) { cmocka_unit_test(test_unknown_arg_type), cmocka_unit_test(test_unknown_return_type), }; - return cmocka_run_group_tests(tests, NULL, NULL); + int result = cmocka_run_group_tests(tests, NULL, NULL); + RuntimeInterface_cleanup(); + return result; }