diff --git a/.gitignore b/.gitignore index 5ace1aa9..4482efb4 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,9 @@ backend-v2/Testing* backend-v2/build_cov/* backend-v2/libbackend_lib.a backend-v2/.cache* +backend-v2/compile_commands.json +backend-v2/clojure-rt.dSYM +backend-v2/CMakeUserPresets.json /.clj-kondo/babashka diff --git a/backend-v2/CMakeLists.txt b/backend-v2/CMakeLists.txt index 3a42f0cb..e4013d11 100644 --- a/backend-v2/CMakeLists.txt +++ b/backend-v2/CMakeLists.txt @@ -1,5 +1,14 @@ cmake_minimum_required(VERSION 3.26) project(clojure-rt) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + + +if(APPLE) + if(NOT CMAKE_OSX_ARCHITECTURES) + set(CMAKE_OSX_ARCHITECTURES "arm64") + endif() + set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf-with-dsym") +endif() message(STATUS "CMAKE_OSX_ARCHITECTURES: ${CMAKE_OSX_ARCHITECTURES}") @@ -22,13 +31,6 @@ set(CMAKE_CXX_FLAGS_RELEASE "-Ofast") set(CMAKE_CXX_STANDARD 20) add_compile_definitions(BUILD_TYPE="${CMAKE_BUILD_TYPE}") -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) # --- Dependency Discovery --- @@ -117,11 +119,16 @@ set(BACKEND_SOURCES codegen/LLVMTypes.cpp codegen/DynamicConstructor.cpp codegen/invoke/InvokeManager.cpp + codegen/invoke/InvokeManager_Math.cpp + codegen/invoke/InvokeManager_Cmp.cpp + codegen/MemoryManagement.cpp codegen/ops/ConstNode.cpp codegen/ops/QuoteNode.cpp codegen/ops/VectorNode.cpp - codegen/ops/MapNode.cpp) + codegen/ops/MapNode.cpp + codegen/ops/StaticCallNode.cpp + codegen/ops/IfNode.cpp) # 2. Protobuf Detection diff --git a/backend-v2/RuntimeHeaders.h b/backend-v2/RuntimeHeaders.h index f9274357..2ea04a97 100644 --- a/backend-v2/RuntimeHeaders.h +++ b/backend-v2/RuntimeHeaders.h @@ -3,12 +3,8 @@ #include -#ifdef __cplusplus -#include -#include -extern "C" { -#endif -#ifdef __cplusplus +// For C++ to understand C's 'restrict' keyword +#if defined(__cplusplus) #if defined(__GNUC__) || defined(__clang__) #define restrict __restrict__ #elif defined(_MSC_VER) @@ -18,21 +14,29 @@ extern "C" { #endif #endif +#include "runtime/ObjectProto.h" +#include "runtime/RTValue.h" +#include "runtime/defines.h" +#include "runtime/word.h" + #include "runtime/BigInteger.h" +#include "runtime/Boolean.h" +#include "runtime/Class.h" +#include "runtime/ConcurrentHashMap.h" +#include "runtime/Double.h" +#include "runtime/Function.h" +#include "runtime/Integer.h" #include "runtime/Keyword.h" +#include "runtime/Nil.h" #include "runtime/Object.h" -#include "runtime/ObjectProto.h" #include "runtime/PersistentArrayMap.h" #include "runtime/PersistentList.h" #include "runtime/PersistentVector.h" -#include "runtime/RTValue.h" +#include "runtime/PersistentVectorIterator.h" +#include "runtime/PersistentVectorNode.h" #include "runtime/Ratio.h" +#include "runtime/RuntimeInterface.h" #include "runtime/String.h" #include "runtime/Symbol.h" -#include "runtime/defines.h" -#include "runtime/word.h" -#ifdef __cplusplus -} -#endif #endif diff --git a/backend-v2/bridge/Exceptions.cpp b/backend-v2/bridge/Exceptions.cpp index 04998b0c..ca7cde5b 100644 --- a/backend-v2/bridge/Exceptions.cpp +++ b/backend-v2/bridge/Exceptions.cpp @@ -1,18 +1,47 @@ #include "Exceptions.h" +#include "../runtime/Exceptions.h" #include #include #include #include +#include +#include #ifdef __APPLE__ +#include #include #elif defined(__linux__) +#include #include #include #endif +#include +#include + namespace rt { +struct JitInfo { + std::string name; + size_t size; + std::unique_ptr objectBuffer; +}; + +static std::mutex gJitMapMutex; +static std::map gJitFunctions; + +extern "C" void registerJitFunction_C(uword_t addr, size_t size, + const char *name, + const void *objData, size_t objSize) { + std::lock_guard lock(gJitMapMutex); + auto buffer = llvm::MemoryBuffer::getMemBufferCopy( + llvm::StringRef(static_cast(objData), objSize), + name ? name : "jit_object"); + gJitFunctions[addr] = {name ? name : "", size, std::move(buffer)}; +} + +// Removing redundant namespace rt { + LanguageException::LanguageException(const std::string &name, RTValue message, RTValue payload) { this->payload = payload; @@ -26,6 +55,30 @@ LanguageException::LanguageException(const std::string &name, RTValue message, } } +LanguageException::LanguageException(const LanguageException &other) { + this->name = other.name; + this->message = other.message; + this->payload = other.payload; + this->stackAddresses = other.stackAddresses; + retain(this->message); + retain(this->payload); +} + +LanguageException & +LanguageException::operator=(const LanguageException &other) { + if (this != &other) { + release(this->message); + release(this->payload); + this->name = other.name; + this->message = other.message; + this->payload = other.payload; + this->stackAddresses = other.stackAddresses; + retain(this->message); + retain(this->payload); + } + return *this; +} + LanguageException::~LanguageException() noexcept { release(message); release(payload); @@ -39,8 +92,8 @@ void LanguageException::printRawTrace() const { std::string LanguageException::toString(llvm::symbolize::LLVMSymbolizer &symbolizer, - const std::string &moduleName, - const intptr_t slide) const { + const std::string &defaultModuleName, + const intptr_t defaultSlide) const { std::stringstream ss; ss << "Exception: " << name << "\n"; @@ -57,41 +110,160 @@ LanguageException::toString(llvm::symbolize::LLVMSymbolizer &symbolizer, ss << "Stack Trace:\n"; bool found = false; for (uword_t addr : stackAddresses) { + std::string currentModule = ""; + uword_t lookupAddr = addr; + uword_t symbolizeAddr = 0; + std::string dlSymbolName = ""; + + Dl_info dlinfo; + bool hasDlInfo = dladdr(reinterpret_cast(addr), &dlinfo); + if (hasDlInfo) { + if (dlinfo.dli_sname) { + dlSymbolName = dlinfo.dli_sname; + } + + // 1. Check if we should start the trace (Found logic) + if (!found && !dlSymbolName.empty() && + dlSymbolName.find("throw") != std::string::npos && + dlSymbolName.find("Exception") != std::string::npos) { + found = true; + continue; + } + +#ifdef __APPLE__ + // 2. Identify module and offset + if (dlinfo.dli_fbase == _dyld_get_image_header(0)) { + // Main executable - must use dSYM and slide + currentModule = defaultModuleName; + lookupAddr = addr - defaultSlide; + } else if (dlinfo.dli_fname) { + currentModule = dlinfo.dli_fname; + lookupAddr = addr - reinterpret_cast(dlinfo.dli_fbase); + } +#else + if (dlinfo.dli_fname) { + currentModule = dlinfo.dli_fname; + lookupAddr = addr - reinterpret_cast(dlinfo.dli_fbase); + } +#endif + } else { + // Check JIT map + std::lock_guard lock(gJitMapMutex); + auto it = gJitFunctions.lower_bound(addr); + if (it != gJitFunctions.begin()) { + auto prev = std::prev(it); + if (addr >= prev->first && addr < prev->first + prev->second.size) { + if (prev->second.objectBuffer) { + uword_t jitSymbolizeAddr = addr - prev->first; +#if defined(__aarch64__) || defined(__arm64__) + if (jitSymbolizeAddr >= 4) + jitSymbolizeAddr -= 4; +#else + if (jitSymbolizeAddr > 0) + jitSymbolizeAddr -= 1; +#endif + auto ObjOrErr = llvm::object::ObjectFile::createObjectFile( + prev->second.objectBuffer->getMemBufferRef()); + if (ObjOrErr) { + auto &Obj = *ObjOrErr.get(); + auto resOrErrJit = symbolizer.symbolizeCode( + Obj, {jitSymbolizeAddr, + llvm::object::SectionedAddress::UndefSection}); + if (resOrErrJit) { + auto &info = resOrErrJit.get(); + if (info.FileName != "") { + ss << " at " << info.FunctionName << " [" << info.FileName + << ":" << info.Line << "]\n"; + found = true; + continue; + } + } else { + llvm::consumeError(resOrErrJit.takeError()); + } + } else { + llvm::consumeError(ObjOrErr.takeError()); + } + } + + if (found) { + ss << " at " << prev->second.name << " [JIT:0x" << std::hex + << prev->first << std::dec << "]\n"; + } + continue; + } + } + } - uword_t lookupAddr = addr - slide; + if (!found) + continue; + + // Adjust address back to get the call site line instead of return site. + symbolizeAddr = lookupAddr; +#if defined(__aarch64__) || defined(__arm64__) + if (symbolizeAddr >= 4) + symbolizeAddr -= 4; +#else + if (symbolizeAddr > 0) + symbolizeAddr -= 1; +#endif + + if (currentModule.empty()) { + currentModule = defaultModuleName; +#if defined(__aarch64__) || defined(__arm64__) + symbolizeAddr = (addr - defaultSlide) - 4; +#else + symbolizeAddr = (addr - defaultSlide) - 1; +#endif + } auto resOrErr = symbolizer.symbolizeCode( - moduleName, {lookupAddr, llvm::object::SectionedAddress::UndefSection}); + currentModule, + {symbolizeAddr, llvm::object::SectionedAddress::UndefSection}); if (resOrErr) { auto &info = resOrErr.get(); + // Second chance for found (if dladdr failed somehow) if (!found && - info.FunctionName == "throwInternalInconsistencyException") { + (info.FunctionName.find("throw") != std::string::npos && + info.FunctionName.find("Exception") != std::string::npos)) { found = true; continue; } + if (found) { ss << " at "; if (info.FunctionName != "") { ss << info.FunctionName; + } else if (!dlSymbolName.empty()) { + ss << dlSymbolName; + } else if (!currentModule.empty()) { + size_t lastSlash = currentModule.find_last_of('/'); + std::string base = (lastSlash != std::string::npos) + ? currentModule.substr(lastSlash + 1) + : currentModule; + ss << base << " + 0x" << std::hex << lookupAddr << std::dec; } else { ss << "0x" << std::hex << std::setw(sizeof(uword_t) * 2) << std::setfill('0') << addr << std::dec; } + if (info.FileName != "") { ss << " [" << info.FileName << ":" << info.Line << "]"; } + ss << "\n"; } } else { + llvm::consumeError(resOrErr.takeError()); if (found) { - ss << " at "; - ss << "?? [0x" << std::hex << addr << std::dec << "]"; - llvm::consumeError(resOrErr.takeError()); + if (!dlSymbolName.empty()) { + ss << " at " << dlSymbolName << " [" << currentModule << "]\n"; + } else { + ss << " at 0x" << std::hex << addr << std::dec << " [" + << currentModule << "]\n"; + } } } - if (found) - ss << "\n"; } return ss.str(); @@ -138,13 +310,64 @@ std::string getExceptionString(const LanguageException &e) { } // namespace rt -extern "C" { void throwInternalInconsistencyException(const std::string &errorMessage) { throw rt::LanguageException( "InternalInconsistencyException", RT_boxPtr(String_createDynamicStr(errorMessage.c_str())), RT_boxNil()); } +extern "C" void +throwInternalInconsistencyException_C(const char *errorMessage) { + throw rt::LanguageException("InternalInconsistencyException", + RT_boxPtr(String_createDynamicStr(errorMessage)), + RT_boxNil()); +} + +extern "C" void throwLanguageException_C(const char *name, RTValue message, + RTValue payload) { + throw rt::LanguageException(name, message, payload); +} + +extern "C" void throwArityException_C(int expected, int actual) { + std::stringstream ss; + ss << "Wrong number of args (" << actual << ") passed to function"; + throw rt::LanguageException( + "ArityException", RT_boxPtr(String_createDynamicStr(ss.str().c_str())), + RT_boxNil()); +} + +extern "C" void throwIllegalArgumentException_C(const char *message) { + throw rt::LanguageException("IllegalArgumentException", + RT_boxPtr(String_createDynamicStr(message)), + RT_boxNil()); +} + +extern "C" void throwIllegalStateException_C(const char *message) { + throw rt::LanguageException("IllegalStateException", + RT_boxPtr(String_createDynamicStr(message)), + RT_boxNil()); +} + +extern "C" void throwUnsupportedOperationException_C(const char *message) { + throw rt::LanguageException("UnsupportedOperationException", + RT_boxPtr(String_createDynamicStr(message)), + RT_boxNil()); +} + +extern "C" void throwArithmeticException_C(const char *message) { + throw rt::LanguageException("ArithmeticException", + RT_boxPtr(String_createDynamicStr(message)), + RT_boxNil()); +} + +extern "C" void throwIndexOutOfBoundsException_C(uword_t index, uword_t count) { + std::stringstream ss; + ss << "Index out of bounds: " << index << " (count: " << count << ")"; + throw rt::LanguageException( + "IndexOutOfBoundsException", + RT_boxPtr(String_createDynamicStr(ss.str().c_str())), RT_boxNil()); +} + void throwCodeGenerationException(const std::string &errorMessage, const Node &node) { std::stringstream retval; @@ -157,4 +380,3 @@ void throwCodeGenerationException(const std::string &errorMessage, "CodeGenerationException", RT_boxPtr(String_createDynamicStr(retval.str().c_str())), RT_boxNil()); } -} diff --git a/backend-v2/bridge/Exceptions.h b/backend-v2/bridge/Exceptions.h index 40307403..d871df81 100644 --- a/backend-v2/bridge/Exceptions.h +++ b/backend-v2/bridge/Exceptions.h @@ -26,6 +26,8 @@ class LanguageException : public std::exception { public: LanguageException(const std::string &name, RTValue message, RTValue payload); + LanguageException(const LanguageException &other); + LanguageException &operator=(const LanguageException &other); ~LanguageException() noexcept override; void printRawTrace() const; std::string toString(llvm::symbolize::LLVMSymbolizer &symbolizer, @@ -37,10 +39,17 @@ 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); -} +// C++-only functions (no extern "C" needed/possible due to std::string/Node) +[[noreturn]] void +throwInternalInconsistencyException(const std::string &errorMessage); +[[noreturn]] void throwCodeGenerationException(const std::string &errorMessage, + const Node &node); + +extern "C" void registerJitFunction_C(uword_t addr, size_t size, + const char *name, + const void *objData, size_t objSize); + +extern "C" [[noreturn]] void +throwInternalInconsistencyException_C(const char *errorMessage); #endif diff --git a/backend-v2/codegen/CodeGen.cpp b/backend-v2/codegen/CodeGen.cpp index fba57b83..4848ac15 100644 --- a/backend-v2/codegen/CodeGen.cpp +++ b/backend-v2/codegen/CodeGen.cpp @@ -7,6 +7,7 @@ using namespace llvm; namespace rt { CodeGenResult CodeGen::release() && { + DIB->finalize(); return {std::move(TSContext), std::move(TheModule), std::move(generatedConstants)}; } @@ -14,14 +15,49 @@ CodeGenResult CodeGen::release() && { std::string CodeGen::codegenTopLevel(const Node &node) { CLJ_ASSERT(TSContext != nullptr, "Codegen was moved"); uword_t i = compilerState.functionAstRegistry.registerObject(&node); - std::string fname = std::string("__anon__") + std::to_string(i); + std::string fname = std::string("__repl__") + std::to_string(i); FunctionType *FT = FunctionType::get(types.i64Ty, {}, false); Function *F = Function::Create(FT, Function::ExternalLinkage, fname, *TheModule); + + // Enforce frame pointers for reliable stack traces + F->addFnAttr("frame-pointer", "all"); + + // Create debug info for the function + auto env = node.env(); + std::string fileName = env.file(); + std::string dir = "."; + if (fileName.empty()) { + fileName = CU->getFilename().str(); + dir = CU->getDirectory().str(); + } else { + size_t lastSlash = fileName.find_last_of('/'); + if (lastSlash != std::string::npos) { + dir = fileName.substr(0, lastSlash); + fileName = fileName.substr(lastSlash + 1); + } + } + + llvm::DIFile *Unit = DIB->createFile(fileName, dir); + llvm::DISubroutineType *AsmSignature = + DIB->createSubroutineType(DIB->getOrCreateTypeArray({})); + llvm::DISubprogram *SP = DIB->createFunction( + Unit, fname, fname, Unit, env.line(), AsmSignature, env.line(), + llvm::DINode::FlagPrototyped, llvm::DISubprogram::SPFlagDefinition); + F->setSubprogram(SP); + LexicalBlocks.push_back(SP); + BasicBlock *BB = BasicBlock::Create(TheContext, "entry", F); Builder.SetInsertPoint(BB); + + // Set initial debug location + Builder.SetCurrentDebugLocation( + llvm::DILocation::get(TheContext, env.line(), env.column(), SP)); + Builder.CreateRet( valueEncoder.box(codegen(node, ObjectTypeSet::all())).value); + + LexicalBlocks.pop_back(); verifyFunction(*F); return fname; } @@ -30,6 +66,12 @@ TypedValue CodeGen::codegen(const Node &node, const ObjectTypeSet &typeRestrictions) { CLJ_ASSERT(TSContext != nullptr, "Codegen was moved"); + auto env = node.env(); + if (!LexicalBlocks.empty()) { + Builder.SetCurrentDebugLocation(llvm::DILocation::get( + TheContext, env.line(), env.column(), LexicalBlocks.back())); + } + for (int i = 0; i < node.dropmemory_size(); i++) { auto guidance = node.dropmemory(i); memoryManagement.dynamicMemoryGuidance(guidance); @@ -44,6 +86,8 @@ TypedValue CodeGen::codegen(const Node &node, return codegen(node, node.subnode().map(), typeRestrictions); case opVector: return codegen(node, node.subnode().vector(), typeRestrictions); + case opStaticCall: + return codegen(node, node.subnode().staticcall(), typeRestrictions); // case opBinding: // return codegen(node, node.subnode().binding(), typeRestrictions); @@ -67,8 +111,8 @@ TypedValue CodeGen::codegen(const Node &node, // return codegen(node, node.subnode().fnmethod(), typeRestrictions); // case opHostInterop: // return codegen(node, node.subnode().hostinterop(), typeRestrictions); - // case opIf: - // return codegen(node, node.subnode().if_(), typeRestrictions); + case opIf: + return codegen(node, node.subnode().if_(), typeRestrictions); // case opImport: // return codegen(node, node.subnode().import(), typeRestrictions); // case opInstanceCall: @@ -109,8 +153,6 @@ TypedValue CodeGen::codegen(const Node &node, // return codegen(node, node.subnode().set(), typeRestrictions); // case opMutateSet: // return codegen(node, node.subnode().mutateset(), typeRestrictions); - // case opStaticCall: - // return codegen(node, node.subnode().staticcall(), typeRestrictions); // case opStaticField: // return codegen(node, node.subnode().staticfield(), typeRestrictions); // case opTheVar: @@ -144,6 +186,8 @@ ObjectTypeSet CodeGen::getType(const Node &node, return getType(node, node.subnode().vector(), typeRestrictions); case opMap: return getType(node, node.subnode().map(), typeRestrictions); + case opStaticCall: + return getType(node, node.subnode().staticcall(), typeRestrictions); // case opBinding: // return getType(node, node.subnode().binding(), typeRestrictions); @@ -167,8 +211,8 @@ ObjectTypeSet CodeGen::getType(const Node &node, // return getType(node, node.subnode().fnmethod(), typeRestrictions); // case opHostInterop: // return getType(node, node.subnode().hostinterop(), typeRestrictions); - // case opIf: - // return getType(node, node.subnode().if_(), typeRestrictions); + case opIf: + return getType(node, node.subnode().if_(), typeRestrictions); // case opImport: // return getType(node, node.subnode().import(), typeRestrictions); // case opInstanceCall: @@ -209,8 +253,6 @@ ObjectTypeSet CodeGen::getType(const Node &node, // return getType(node, node.subnode().set(), typeRestrictions); // case opMutateSet: // return getType(node, node.subnode().mutateset(), typeRestrictions); - // case opStaticCall: - // return getType(node, node.subnode().staticcall(), typeRestrictions); // case opStaticField: // return getType(node, node.subnode().staticfield(), typeRestrictions); // case opTheVar: @@ -232,4 +274,5 @@ ObjectTypeSet CodeGen::getType(const Node &node, return ObjectTypeSet::all(); } + } // namespace rt diff --git a/backend-v2/codegen/CodeGen.h b/backend-v2/codegen/CodeGen.h index 8eb00c3a..fcb15be7 100644 --- a/backend-v2/codegen/CodeGen.h +++ b/backend-v2/codegen/CodeGen.h @@ -11,6 +11,7 @@ #include "bytecode.pb.h" #include "invoke/InvokeManager.h" #include +#include #include #include #include @@ -18,16 +19,28 @@ #include #include #include +#include +#include using namespace clojure::rt::protobuf::bytecode; namespace rt { + struct CodeGenResult { std::unique_ptr context; std::unique_ptr module; std::vector constants; }; +class CodeGenerationException : public std::runtime_error { + const Node *node; + +public: + CodeGenerationException(const std::string &msg, const Node &n) + : std::runtime_error(msg), node(&n) {} + const Node &getNode() const { return *node; } +}; + class CodeGen { std::unique_ptr TSContext; @@ -46,6 +59,9 @@ class CodeGen { MemoryManagement memoryManagement; ThreadsafeCompilerState &compilerState; + std::unique_ptr DIB; + llvm::DICompileUnit *CU; + std::vector LexicalBlocks; public: CodeGen(std::string_view ModuleName, ThreadsafeCompilerState &state) @@ -60,7 +76,17 @@ class CodeGen { memoryManagement(TheContext, Builder, valueEncoder, types, variableBindingStack, invokeManager, dynamicConstructor), - compilerState(state) {} + compilerState(state) { + TheModule->addModuleFlag(llvm::Module::Warning, "Debug Info Version", + llvm::DEBUG_METADATA_VERSION); +#ifdef __APPLE__ + TheModule->addModuleFlag(llvm::Module::Warning, "Dwarf Version", 4); +#endif + DIB = std::make_unique(*TheModule); + CU = DIB->createCompileUnit(llvm::dwarf::DW_LANG_C, + DIB->createFile(ModuleName, "."), "clojure-rt", + false, "", 0); + } CodeGenResult release() &&; @@ -76,67 +102,14 @@ class CodeGen { const ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, const VectorNode &subnode, const ObjectTypeSet &typeRestrictions); + TypedValue codegen(const Node &node, const StaticCallNode &subnode, + const ObjectTypeSet &typeRestrictions); + + TypedValue codegen(const Node &node, const IfNode &subnode, + const ObjectTypeSet &typeRestrictions); + ObjectTypeSet getType(const Node &node, const IfNode &subnode, + const ObjectTypeSet &typeRestrictions); - // TypedValue codegen(const Node &node, const BindingNode &subnode, const - // ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, - // const CaseNode &subnode, const ObjectTypeSet &typeRestrictions); TypedValue - // codegen(const Node &node, const CaseTestNode &subnode, const ObjectTypeSet - // &typeRestrictions); TypedValue codegen(const Node &node, const CaseThenNode - // &subnode, const ObjectTypeSet &typeRestrictions); TypedValue codegen(const - // Node &node, const CatchNode &subnode, const ObjectTypeSet - // &typeRestrictions); - - // TypedValue codegen(const Node &node, const DefNode &subnode, const - // ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, - // const DeftypeNode &subnode, const ObjectTypeSet &typeRestrictions); - // TypedValue codegen(const Node &node, const DoNode &subnode, const - // ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, - // const FnNode &subnode, const ObjectTypeSet &typeRestrictions); TypedValue - // codegen(const Node &node, const FnMethodNode &subnode, const ObjectTypeSet - // &typeRestrictions); TypedValue codegen(const Node &node, const - // HostInteropNode &subnode, const ObjectTypeSet &typeRestrictions); - // TypedValue codegen(const Node &node, const IfNode &subnode, const - // ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, - // const ImportNode &subnode, const ObjectTypeSet &typeRestrictions); - // TypedValue codegen(const Node &node, const InstanceCallNode &subnode, const - // ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, - // const InstanceFieldNode &subnode, const ObjectTypeSet &typeRestrictions); - // TypedValue codegen(const Node &node, const IsInstanceNode &subnode, const - // ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, - // const InvokeNode &subnode, const ObjectTypeSet &typeRestrictions); - // TypedValue codegen(const Node &node, const KeywordInvokeNode &subnode, - // const ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node - // &node, const LetNode &subnode, const ObjectTypeSet &typeRestrictions); - // TypedValue codegen(const Node &node, const LetfnNode &subnode, const - // ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, - // const LocalNode &subnode, const ObjectTypeSet &typeRestrictions); - // TypedValue codegen(const Node &node, const LoopNode &subnode, const - // ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, - // const MethodNode &subnode, const ObjectTypeSet &typeRestrictions); - // TypedValue codegen(const Node &node, const MonitorEnterNode &subnode, const - // ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, - // const MonitorExitNode &subnode, const ObjectTypeSet &typeRestrictions); - // TypedValue codegen(const Node &node, const NewNode &subnode, const - // ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, - // const PrimInvokeNode &subnode, const ObjectTypeSet &typeRestrictions); - // TypedValue codegen(const Node &node, const ProtocolInvokeNode &subnode, - // const ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node - // &node, const RecurNode &subnode, const ObjectTypeSet &typeRestrictions); - // TypedValue codegen(const Node &node, const ReifyNode &subnode, const - // ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, - // const SetNode &subnode, const ObjectTypeSet &typeRestrictions); TypedValue - // codegen(const Node &node, const MutateSetNode &subnode, const ObjectTypeSet - // &typeRestrictions); TypedValue codegen(const Node &node, const - // StaticCallNode &subnode, const ObjectTypeSet &typeRestrictions); TypedValue - // codegen(const Node &node, const StaticFieldNode &subnode, const - // ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, - // const TheVarNode &subnode, const ObjectTypeSet &typeRestrictions); - // TypedValue codegen(const Node &node, const ThrowNode &subnode, const - // ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, - // const TryNode &subnode, const ObjectTypeSet &typeRestrictions); TypedValue - // codegen(const Node &node, const VarNode &subnode, const ObjectTypeSet - // &typeRestrictions); TypedValue codegen(const Node &node, const WithMetaNode - // &subnode, const ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, const ObjectTypeSet &typeRestrictions); @@ -149,70 +122,8 @@ class CodeGen { const ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, const MapNode &subnode, const ObjectTypeSet &typeRestrictions); - // ObjectTypeSet getType(const Node &node, const BindingNode &subnode, const - // ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, - // const CaseNode &subnode, const ObjectTypeSet &typeRestrictions); - // ObjectTypeSet getType(const Node &node, const CaseTestNode &subnode, const - // ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, - // const CaseThenNode &subnode, const ObjectTypeSet &typeRestrictions); - // ObjectTypeSet getType(const Node &node, const CatchNode &subnode, const - // ObjectTypeSet &typeRestrictions); - - // ObjectTypeSet getType(const Node &node, const DefNode &subnode, const - // ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, - // const DeftypeNode &subnode, const ObjectTypeSet &typeRestrictions); - // ObjectTypeSet getType(const Node &node, const DoNode &subnode, const - // ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, - // const FnNode &subnode, const ObjectTypeSet &typeRestrictions); - // ObjectTypeSet getType(const Node &node, const FnMethodNode &subnode, const - // ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, - // const HostInteropNode &subnode, const ObjectTypeSet &typeRestrictions); - // ObjectTypeSet getType(const Node &node, const IfNode &subnode, const - // ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, - // const ImportNode &subnode, const ObjectTypeSet &typeRestrictions); - // ObjectTypeSet getType(const Node &node, const InstanceCallNode &subnode, - // const ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node - // &node, const InstanceFieldNode &subnode, const ObjectTypeSet - // &typeRestrictions); ObjectTypeSet getType(const Node &node, const - // IsInstanceNode &subnode, const ObjectTypeSet &typeRestrictions); - // ObjectTypeSet getType(const Node &node, const InvokeNode &subnode, const - // ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, - // const KeywordInvokeNode &subnode, const ObjectTypeSet &typeRestrictions); - // ObjectTypeSet getType(const Node &node, const LetNode &subnode, const - // ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, - // const LetfnNode &subnode, const ObjectTypeSet &typeRestrictions); - // ObjectTypeSet getType(const Node &node, const LocalNode &subnode, const - // ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, - // const LoopNode &subnode, const ObjectTypeSet &typeRestrictions); - - // ObjectTypeSet getType(const Node &node, const MethodNode &subnode, const - // ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, - // const MonitorEnterNode &subnode, const ObjectTypeSet &typeRestrictions); - // ObjectTypeSet getType(const Node &node, const MonitorExitNode &subnode, - // const ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node - // &node, const NewNode &subnode, const ObjectTypeSet &typeRestrictions); - // ObjectTypeSet getType(const Node &node, const PrimInvokeNode &subnode, - // const ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node - // &node, const ProtocolInvokeNode &subnode, const ObjectTypeSet - // &typeRestrictions); ObjectTypeSet getType(const Node &node, const RecurNode - // &subnode, const ObjectTypeSet &typeRestrictions); ObjectTypeSet - // getType(const Node &node, const ReifyNode &subnode, const ObjectTypeSet - // &typeRestrictions); ObjectTypeSet getType(const Node &node, const SetNode - // &subnode, const ObjectTypeSet &typeRestrictions); ObjectTypeSet - // getType(const Node &node, const MutateSetNode &subnode, const ObjectTypeSet - // &typeRestrictions); ObjectTypeSet getType(const Node &node, const - // StaticCallNode &subnode, const ObjectTypeSet &typeRestrictions); - // ObjectTypeSet getType(const Node &node, const StaticFieldNode &subnode, - // const ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node - // &node, const TheVarNode &subnode, const ObjectTypeSet &typeRestrictions); - // ObjectTypeSet getType(const Node &node, const ThrowNode &subnode, const - // ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, - // const TryNode &subnode, const ObjectTypeSet &typeRestrictions); - // ObjectTypeSet getType(const Node &node, const VarNode &subnode, const - // ObjectTypeSet &typeRestrictions); - - // ObjectTypeSet getType(const Node &node, const WithMetaNode &subnode, const - // ObjectTypeSet &typeRestrictions); + ObjectTypeSet getType(const Node &node, const StaticCallNode &subnode, + const ObjectTypeSet &typeRestrictions); }; } // namespace rt diff --git a/backend-v2/codegen/DynamicConstructor.cpp b/backend-v2/codegen/DynamicConstructor.cpp index 7ecb5728..47341ecb 100644 --- a/backend-v2/codegen/DynamicConstructor.cpp +++ b/backend-v2/codegen/DynamicConstructor.cpp @@ -101,7 +101,7 @@ TypedValue DynamicConstructor::createRatio(const char *s) { generatedConstants.push_back(RT_boxPtr(retVal)); uintptr_t address = reinterpret_cast(retVal); return TypedValue( - ObjectTypeSet(bigIntegerType, false, new ConstantRatio(std::string(s))), + ObjectTypeSet(ratioType, false, new ConstantRatio(std::string(s))), ConstantExpr::getIntToPtr(ConstantInt::get(types.i64Ty, address), types.ptrTy)); } diff --git a/backend-v2/codegen/MemoryManagement.h b/backend-v2/codegen/MemoryManagement.h index 5a9bcddd..bca9ad71 100644 --- a/backend-v2/codegen/MemoryManagement.h +++ b/backend-v2/codegen/MemoryManagement.h @@ -1,43 +1,44 @@ #ifndef MEMORY_MANAGEMENT_H #define MEMORY_MANAGEMENT_H -#include -#include -#include -#include #include "DynamicConstructor.h" +#include "LLVMTypes.h" #include "TypedValue.h" #include "ValueEncoder.h" -#include "LLVMTypes.h" #include "VariableBindings.h" -#include "invoke/InvokeManager.h" #include "bytecode.pb.h" +#include "invoke/InvokeManager.h" +#include +#include +#include +#include using namespace clojure::rt::protobuf::bytecode; namespace rt { - - class MemoryManagement { - private: - llvm::LLVMContext &context; - llvm::IRBuilder<> &builder; - ValueEncoder &valueEncoder; - LLVMTypes &types; - VariableBindings &variableBindingStack; - InvokeManager &invoke; - DynamicConstructor &dynamicConstructor; - public: - explicit MemoryManagement(llvm::LLVMContext &context, llvm::IRBuilder<> &b, - ValueEncoder &v, LLVMTypes &t, - VariableBindings &vb, - InvokeManager &i, DynamicConstructor &d); - - void dynamicMemoryGuidance(const MemoryManagementGuidance &guidance); - - void dynamicRetain(TypedValue &target); - TypedValue dynamicRelease(TypedValue &target); - void dynamicIsReusable(TypedValue &target); - }; + +class MemoryManagement { +private: + llvm::LLVMContext &context; + llvm::IRBuilder<> &builder; + ValueEncoder &valueEncoder; + LLVMTypes &types; + VariableBindings &variableBindingStack; + InvokeManager &invoke; + DynamicConstructor &dynamicConstructor; + +public: + explicit MemoryManagement(llvm::LLVMContext &context, llvm::IRBuilder<> &b, + ValueEncoder &v, LLVMTypes &t, + VariableBindings &vb, InvokeManager &i, + DynamicConstructor &d); + + void dynamicMemoryGuidance(const MemoryManagementGuidance &guidance); + + void dynamicRetain(TypedValue &target); + TypedValue dynamicRelease(TypedValue &target); + void dynamicIsReusable(TypedValue &target); +}; } // namespace rt diff --git a/backend-v2/codegen/TypedValue.h b/backend-v2/codegen/TypedValue.h index 754bd0bf..fb087fe4 100644 --- a/backend-v2/codegen/TypedValue.h +++ b/backend-v2/codegen/TypedValue.h @@ -1,15 +1,14 @@ #ifndef TYPED_VALUE_H #define TYPED_VALUE_H -#include "llvm/IR/Type.h" #include "../types/ObjectTypeSet.h" +#include "llvm/IR/Type.h" namespace rt { - struct TypedValue { - ObjectTypeSet type; - llvm::Value *value; - TypedValue(const ObjectTypeSet &t, llvm::Value *v): type(t), value(v) {} - }; +struct TypedValue { + ObjectTypeSet type; + llvm::Value *value; + TypedValue(const ObjectTypeSet &t, llvm::Value *v) : type(t), value(v) {} +}; } // namespace rt - #endif diff --git a/backend-v2/codegen/ValueEncoder.cpp b/backend-v2/codegen/ValueEncoder.cpp index 83eeaac9..cbc99266 100644 --- a/backend-v2/codegen/ValueEncoder.cpp +++ b/backend-v2/codegen/ValueEncoder.cpp @@ -1,306 +1,389 @@ #include "ValueEncoder.h" +#include "../bridge/Exceptions.h" #include "../cljassert.h" #include "../runtime/word.h" #include "LLVMTypes.h" #include "TypedValue.h" -#include -#include "../bridge/Exceptions.h" #include +#include using namespace llvm; using namespace std; -namespace rt { - ValueEncoder::ValueEncoder(LLVMContext &ctx, IRBuilder<> &b, LLVMTypes &t) - : context(ctx), builder(b), types(t), word_size(K_WORD_SIZE*8) {} - - llvm::Value *ValueEncoder::u64(uint64_t val) { - return ConstantInt::get(context, APInt(64, val)); - } - - // --- BOXING --- - - TypedValue ValueEncoder::box(TypedValue val) { - if (val.type.isBoxedType()) - return val; +namespace rt { +ValueEncoder::ValueEncoder(LLVMContext &ctx, IRBuilder<> &b, LLVMTypes &t) + : context(ctx), builder(b), types(t), word_size(K_WORD_SIZE * 8) {} - if (val.type.isUnboxedPointer()) { - return boxPointer(val); - } +llvm::Value *ValueEncoder::u64(uint64_t val) { + return ConstantInt::get(context, APInt(64, val)); +} - if (val.type.isUnboxedType(doubleType)) { - return boxDouble(val); - } +// --- BOXING --- - if (val.type.isUnboxedType(integerType)) { - return boxInt32(val); - } - - if (val.type.isUnboxedType(booleanType)) { - return boxBool(val); - } - - if (val.type.isUnboxedType(keywordType)) { - return boxKeyword(val); - } - - if (val.type.isUnboxedType(symbolType)) { - return boxSymbol(val); - } - - /* This should not happen, but we keep the check nevertheless */ - if (val.type.isUnboxedType(nilType)) { - return boxNil(); - } - - throwInternalInconsistencyException("Unallowed type for boxing: " + - val.type.toString()); +TypedValue ValueEncoder::box(TypedValue val) { + if (val.type.isBoxedType()) return val; - } - TypedValue ValueEncoder::boxDouble(TypedValue doubleVal) { - if (!doubleVal.type.isUnboxedType(doubleType)) - return doubleVal; - return TypedValue( - doubleVal.type.boxed(), - builder.CreateBitCast(doubleVal.value, types.i64Ty, "box_dbl")); - } - - TypedValue ValueEncoder::boxInt32(TypedValue int32Val) { - if (!int32Val.type.isUnboxedType(integerType)) - return int32Val; - llvm::Value *val64 = builder.CreateZExt(int32Val.value, types.i64Ty); - return TypedValue(int32Val.type.boxed(), - builder.CreateOr(val64, u64(RT_TAG_INT32), "box_int")); + if (val.type.isUnboxedPointer()) { + return boxPointer(val); } - TypedValue ValueEncoder::boxBool(TypedValue boolVal) { - if (!boolVal.type.isUnboxedType(booleanType)) - return boolVal; - llvm::Value *val64 = builder.CreateZExt(boolVal.value, types.i64Ty); - return TypedValue(boolVal.type.boxed(), - builder.CreateOr(val64, u64(RT_TAG_BOOL), "box_bool")); + if (val.type.isUnboxedType(doubleType)) { + return boxDouble(val); } - TypedValue ValueEncoder::boxNil() { - return TypedValue(ObjectTypeSet(nilType, true, new ConstantNil()), - u64(RT_TAG_NIL)); + if (val.type.isUnboxedType(integerType)) { + return boxInt32(val); } - TypedValue ValueEncoder::boxPointer(TypedValue rawPtr) { - if (!rawPtr.type.isUnboxedPointer()) - return rawPtr; - // Cast Ptr -> Int. - // On 32-bit arch, this results in i32, so we must ZExt to i64. - llvm::Value *ptrAsInt = builder.CreatePtrToInt( - rawPtr.value, word_size == 64 ? types.i64Ty : types.i32Ty); - - if (word_size == 32) { - ptrAsInt = builder.CreateZExt(ptrAsInt, types.i64Ty); - } - return TypedValue(rawPtr.type.boxed(), - builder.CreateOr(ptrAsInt, u64(RT_TAG_PTR), "box_ptr")); + if (val.type.isUnboxedType(booleanType)) { + return boxBool(val); } - TypedValue ValueEncoder::boxKeyword(TypedValue keywordId) { - if (!keywordId.type.isUnboxedType(keywordType)) - return keywordId; - // keywordId should be an i32 ID - llvm::Value *val64 = builder.CreateZExt(keywordId.value, types.i64Ty); - return TypedValue(keywordId.type.boxed(), - builder.CreateOr(val64, u64(RT_TAG_KEYWORD), "box_kw")); + if (val.type.isUnboxedType(keywordType)) { + return boxKeyword(val); } - TypedValue ValueEncoder::boxSymbol(TypedValue symbolId) { - if (!symbolId.type.isUnboxedType(symbolType)) - return symbolId; - // symbolId should be an i32 ID - llvm::Value *val64 = builder.CreateZExt(symbolId.value, types.i64Ty); - return TypedValue(symbolId.type.boxed(), - builder.CreateOr(val64, u64(RT_TAG_SYMBOL), "box_kw")); + if (val.type.isUnboxedType(symbolType)) { + return boxSymbol(val); } - // --- TYPE CHECKING --- - - TypedValue ValueEncoder::isDouble(TypedValue boxedVal) { - CLJ_ASSERT(boxedVal.type.isBoxedType(), - "Unboxed value passed to ValueEncoder"); - if (boxedVal.type.isBoxedType(doubleType)) - return TypedValue( - ObjectTypeSet(booleanType, false, new ConstantBoolean(true)), - builder.getInt1(true)); - // Unsigned Less Than "Start of Tag Space" means it is a double - return TypedValue( - ObjectTypeSet(booleanType, false), - builder.CreateICmpULT(boxedVal.value, u64(RT_TAG_DOUBLE_START), "is_dbl")); + /* This should not happen, but we keep the check nevertheless */ + if (val.type.isUnboxedType(nilType)) { + return boxNil(); } - TypedValue ValueEncoder::isInt32(TypedValue boxedVal) { - CLJ_ASSERT(boxedVal.type.isBoxedType(), - "Unboxed value passed to ValueEncoder"); - if (boxedVal.type.isBoxedType(integerType)) - return TypedValue( - ObjectTypeSet(booleanType, false, new ConstantBoolean(true)), - builder.getInt1(true)); - llvm::Value *masked = builder.CreateAnd(boxedVal.value, u64(RT_TAG_MASK)); - return TypedValue(ObjectTypeSet(booleanType, false), - builder.CreateICmpEQ(masked, u64(RT_TAG_INT32), "is_int")); + throwInternalInconsistencyException("Unallowed type for boxing: " + + val.type.toString()); + return val; +} + +TypedValue ValueEncoder::boxDouble(TypedValue doubleVal) { + if (!doubleVal.type.isUnboxedType(doubleType)) + return doubleVal; + return TypedValue( + doubleVal.type.boxed(), + builder.CreateBitCast(doubleVal.value, types.i64Ty, "box_dbl")); +} + +TypedValue ValueEncoder::boxInt32(TypedValue int32Val) { + if (!int32Val.type.isUnboxedType(integerType)) + return int32Val; + llvm::Value *val64 = builder.CreateZExt(int32Val.value, types.i64Ty); + return TypedValue(int32Val.type.boxed(), + builder.CreateOr(val64, u64(RT_TAG_INT32), "box_int")); +} + +TypedValue ValueEncoder::boxBool(TypedValue boolVal) { + if (!boolVal.type.isUnboxedType(booleanType)) + return boolVal; + llvm::Value *val64 = builder.CreateZExt(boolVal.value, types.i64Ty); + return TypedValue(boolVal.type.boxed(), + builder.CreateOr(val64, u64(RT_TAG_BOOL), "box_bool")); +} + +TypedValue ValueEncoder::boxNil() { + return TypedValue(ObjectTypeSet(nilType, true, new ConstantNil()), + u64(RT_TAG_NIL)); +} + +TypedValue ValueEncoder::boxPointer(TypedValue rawPtr) { + if (!rawPtr.type.isUnboxedPointer()) + return rawPtr; + // Cast Ptr -> Int. + // On 32-bit arch, this results in i32, so we must ZExt to i64. + llvm::Value *ptrAsInt = builder.CreatePtrToInt( + rawPtr.value, word_size == 64 ? types.i64Ty : types.i32Ty); + + if (word_size == 32) { + ptrAsInt = builder.CreateZExt(ptrAsInt, types.i64Ty); } - - TypedValue ValueEncoder::isBool(TypedValue boxedVal) { - CLJ_ASSERT(boxedVal.type.isBoxedType(), - "Unboxed value passed to ValueEncoder"); - if (boxedVal.type.isBoxedType(booleanType)) - return TypedValue( - ObjectTypeSet(booleanType, false, new ConstantBoolean(true)), - builder.getInt1(true)); - llvm::Value *masked = builder.CreateAnd(boxedVal.value, u64(RT_TAG_MASK)); - return TypedValue(ObjectTypeSet(booleanType, false), - builder.CreateICmpEQ(masked, u64(RT_TAG_BOOL), "is_bool")); - } - - TypedValue ValueEncoder::isNil(TypedValue boxedVal) { - CLJ_ASSERT(boxedVal.type.isBoxedType(), - "Unboxed value passed to ValueEncoder"); - if (boxedVal.type.isBoxedType(nilType)) - return TypedValue( - ObjectTypeSet(booleanType, false, new ConstantBoolean(true)), - builder.getInt1(true)); + return TypedValue(rawPtr.type.boxed(), + builder.CreateOr(ptrAsInt, u64(RT_TAG_PTR), "box_ptr")); +} + +TypedValue ValueEncoder::boxKeyword(TypedValue keywordId) { + if (!keywordId.type.isUnboxedType(keywordType)) + return keywordId; + // keywordId should be an i32 ID + llvm::Value *val64 = builder.CreateZExt(keywordId.value, types.i64Ty); + return TypedValue(keywordId.type.boxed(), + builder.CreateOr(val64, u64(RT_TAG_KEYWORD), "box_kw")); +} + +TypedValue ValueEncoder::boxSymbol(TypedValue symbolId) { + if (!symbolId.type.isUnboxedType(symbolType)) + return symbolId; + // symbolId should be an i32 ID + llvm::Value *val64 = builder.CreateZExt(symbolId.value, types.i64Ty); + return TypedValue(symbolId.type.boxed(), + builder.CreateOr(val64, u64(RT_TAG_SYMBOL), "box_kw")); +} + +// --- TYPE CHECKING --- + +TypedValue ValueEncoder::isDouble(TypedValue boxedVal) { + CLJ_ASSERT(boxedVal.type.isBoxedType(), + "Unboxed value passed to ValueEncoder"); + if (boxedVal.type.isBoxedType(doubleType)) return TypedValue( - ObjectTypeSet(booleanType, false), - builder.CreateICmpEQ(boxedVal.value, u64(RT_TAG_NIL), "is_null")); - } - - TypedValue ValueEncoder::isPointer(TypedValue boxedVal) { - CLJ_ASSERT(boxedVal.type.isBoxedType(), - "Unboxed value passed to ValueEncoder"); - if (boxedVal.type.isBoxedPointer()) - return TypedValue( - ObjectTypeSet(booleanType, false, new ConstantBoolean(true)), - builder.getInt1(true)); - llvm::Value *masked = builder.CreateAnd(boxedVal.value, u64(RT_TAG_MASK)); - return TypedValue(ObjectTypeSet(booleanType, false), - builder.CreateICmpEQ(masked, u64(RT_TAG_PTR), "is_ptr")); - } - - TypedValue ValueEncoder::isKeyword(TypedValue boxedVal) { - CLJ_ASSERT(boxedVal.type.isBoxedType(), - "Unboxed value passed to ValueEncoder"); - if (boxedVal.type.isBoxedType(keywordType)) - return TypedValue( - ObjectTypeSet(booleanType, false, new ConstantBoolean(true)), - builder.getInt1(true)); - llvm::Value *masked = builder.CreateAnd(boxedVal.value, u64(RT_TAG_MASK)); - return TypedValue(ObjectTypeSet(booleanType, false), - builder.CreateICmpEQ(masked, u64(RT_TAG_KEYWORD), "is_kw")); - } - - TypedValue ValueEncoder::isSymbol(TypedValue boxedVal) { - CLJ_ASSERT(boxedVal.type.isBoxedType(), - "Unboxed value passed to ValueEncoder"); - if (boxedVal.type.isBoxedType(symbolType)) - return TypedValue( - ObjectTypeSet(booleanType, false, new ConstantBoolean(true)), - builder.getInt1(true)); - llvm::Value *masked = builder.CreateAnd(boxedVal.value, u64(RT_TAG_MASK)); - return TypedValue(ObjectTypeSet(booleanType, false), - builder.CreateICmpEQ(masked, u64(RT_TAG_SYMBOL), "is_sym")); - } - - // --- UNBOXING --- - - TypedValue ValueEncoder::unbox(TypedValue val) { - if (!val.type.isBoxedType()) - return val; - - if (val.type.isBoxedPointer()) { - return unboxPointer(val); - } - - if (val.type.isBoxedType(doubleType)) { - return unboxDouble(val); - } - - if (val.type.isBoxedType(integerType)) { - return unboxInt32(val); - } - - if (val.type.isBoxedType(booleanType)) { - return unboxBool(val); - } - - if (val.type.isBoxedType(keywordType)) { - return unboxKeyword(val); - } + ObjectTypeSet(booleanType, false, new ConstantBoolean(true)), + builder.getInt1(true)); + // Unsigned Less Than "Start of Tag Space" means it is a double + return TypedValue(ObjectTypeSet(booleanType, false), + builder.CreateICmpULT(boxedVal.value, + u64(RT_TAG_DOUBLE_START), "is_dbl")); +} + +TypedValue ValueEncoder::isInt32(TypedValue boxedVal) { + CLJ_ASSERT(boxedVal.type.isBoxedType(), + "Unboxed value passed to ValueEncoder"); + if (boxedVal.type.isBoxedType(integerType)) + return TypedValue( + ObjectTypeSet(booleanType, false, new ConstantBoolean(true)), + builder.getInt1(true)); + llvm::Value *masked = builder.CreateAnd(boxedVal.value, u64(RT_TAG_MASK)); + return TypedValue(ObjectTypeSet(booleanType, false), + builder.CreateICmpEQ(masked, u64(RT_TAG_INT32), "is_int")); +} + +TypedValue ValueEncoder::isBool(TypedValue boxedVal) { + CLJ_ASSERT(boxedVal.type.isBoxedType(), + "Unboxed value passed to ValueEncoder"); + if (boxedVal.type.isBoxedType(booleanType)) + return TypedValue( + ObjectTypeSet(booleanType, false, new ConstantBoolean(true)), + builder.getInt1(true)); + llvm::Value *masked = builder.CreateAnd(boxedVal.value, u64(RT_TAG_MASK)); + return TypedValue(ObjectTypeSet(booleanType, false), + builder.CreateICmpEQ(masked, u64(RT_TAG_BOOL), "is_bool")); +} + +TypedValue ValueEncoder::isNil(TypedValue boxedVal) { + CLJ_ASSERT(boxedVal.type.isBoxedType(), + "Unboxed value passed to ValueEncoder"); + if (boxedVal.type.isBoxedType(nilType)) + return TypedValue( + ObjectTypeSet(booleanType, false, new ConstantBoolean(true)), + builder.getInt1(true)); + return TypedValue( + ObjectTypeSet(booleanType, false), + builder.CreateICmpEQ(boxedVal.value, u64(RT_TAG_NIL), "is_null")); +} + +TypedValue ValueEncoder::isPointer(TypedValue boxedVal) { + CLJ_ASSERT(boxedVal.type.isBoxedType(), + "Unboxed value passed to ValueEncoder"); + if (boxedVal.type.isBoxedPointer()) + return TypedValue( + ObjectTypeSet(booleanType, false, new ConstantBoolean(true)), + builder.getInt1(true)); + llvm::Value *masked = builder.CreateAnd(boxedVal.value, u64(RT_TAG_MASK)); + return TypedValue(ObjectTypeSet(booleanType, false), + builder.CreateICmpEQ(masked, u64(RT_TAG_PTR), "is_ptr")); +} + +TypedValue ValueEncoder::isKeyword(TypedValue boxedVal) { + CLJ_ASSERT(boxedVal.type.isBoxedType(), + "Unboxed value passed to ValueEncoder"); + if (boxedVal.type.isBoxedType(keywordType)) + return TypedValue( + ObjectTypeSet(booleanType, false, new ConstantBoolean(true)), + builder.getInt1(true)); + llvm::Value *masked = builder.CreateAnd(boxedVal.value, u64(RT_TAG_MASK)); + return TypedValue(ObjectTypeSet(booleanType, false), + builder.CreateICmpEQ(masked, u64(RT_TAG_KEYWORD), "is_kw")); +} + +TypedValue ValueEncoder::isBigInt(TypedValue boxedVal) { + CLJ_ASSERT(boxedVal.type.isBoxedType(), + "Unboxed value passed to ValueEncoder"); + if (boxedVal.type.isBoxedType(bigIntegerType)) + return TypedValue(ObjectTypeSet(booleanType, false), builder.getInt1(true)); + + Value *isPtr = isPointer(boxedVal).value; + + // We need to check the type field in the header. + // For simplicity and to avoid complex branching here, we can do it with a + // select if we are careful, but a branch is safer to avoid illegal memory + // access on non-pointers. However, LLVM handles AND + ICMP fine. The type + // check itself needs a valid pointer. + + Function *currentFn = builder.GetInsertBlock()->getParent(); + BasicBlock *checkTypeBB = + BasicBlock::Create(context, "check_obj_type", currentFn); + BasicBlock *mergeBB = + BasicBlock::Create(context, "obj_type_merge", currentFn); + + Value *earlyResult = builder.getFalse(); + BasicBlock *earlyBB = builder.GetInsertBlock(); + builder.CreateCondBr(isPtr, checkTypeBB, mergeBB); + + builder.SetInsertPoint(checkTypeBB); + Value *rawPtr = + builder.CreateAnd(boxedVal.value, u64(~RT_TAG_MASK), "raw_ptr"); + Value *ptr = builder.CreateIntToPtr(rawPtr, types.ptrTy); + + // Type is at offset: wordTy (atomicRefCount) + Value *typePtr = + builder.CreateStructGEP(types.RT_objectTy, ptr, 1, "type_ptr"); + Value *typeVal = builder.CreateLoad(types.i32Ty, typePtr, "obj_type"); + Value *isBigInt = builder.CreateICmpEQ( + typeVal, ConstantInt::get(types.i32Ty, bigIntegerType), "is_bigint"); + BasicBlock *finalCheckBB = builder.GetInsertBlock(); + builder.CreateBr(mergeBB); + + builder.SetInsertPoint(mergeBB); + PHINode *phi = builder.CreatePHI(types.i1Ty, 2, "is_bigint_phi"); + phi->addIncoming(earlyResult, earlyBB); + phi->addIncoming(isBigInt, finalCheckBB); + + return TypedValue(ObjectTypeSet(booleanType, false), phi); +} + +TypedValue ValueEncoder::isRatio(TypedValue boxedVal) { + CLJ_ASSERT(boxedVal.type.isBoxedType(), + "Unboxed value passed to ValueEncoder"); + if (boxedVal.type.isBoxedType(ratioType)) + return TypedValue(ObjectTypeSet(booleanType, false), builder.getInt1(true)); + + Value *isPtr = isPointer(boxedVal).value; + Function *currentFn = builder.GetInsertBlock()->getParent(); + BasicBlock *checkTypeBB = + BasicBlock::Create(context, "check_ratio_type", currentFn); + BasicBlock *mergeBB = + BasicBlock::Create(context, "ratio_type_merge", currentFn); + + Value *earlyResult = builder.getFalse(); + BasicBlock *earlyBB = builder.GetInsertBlock(); + builder.CreateCondBr(isPtr, checkTypeBB, mergeBB); + + builder.SetInsertPoint(checkTypeBB); + Value *rawPtr = + builder.CreateAnd(boxedVal.value, u64(~RT_TAG_MASK), "raw_ptr_ratio"); + Value *ptr = builder.CreateIntToPtr(rawPtr, types.ptrTy); + + Value *typePtr = + builder.CreateStructGEP(types.RT_objectTy, ptr, 1, "type_ptr_ratio"); + Value *typeVal = builder.CreateLoad(types.i32Ty, typePtr, "obj_type_ratio"); + Value *isRatio = builder.CreateICmpEQ( + typeVal, ConstantInt::get(types.i32Ty, ratioType), "is_ratio"); + BasicBlock *finalCheckBB = builder.GetInsertBlock(); + builder.CreateBr(mergeBB); + + builder.SetInsertPoint(mergeBB); + PHINode *phi = builder.CreatePHI(types.i1Ty, 2, "is_ratio_phi"); + phi->addIncoming(earlyResult, earlyBB); + phi->addIncoming(isRatio, finalCheckBB); + + return TypedValue(ObjectTypeSet(booleanType, false), phi); +} + +TypedValue ValueEncoder::isSymbol(TypedValue boxedVal) { + CLJ_ASSERT(boxedVal.type.isBoxedType(), + "Unboxed value passed to ValueEncoder"); + if (boxedVal.type.isBoxedType(symbolType)) + return TypedValue( + ObjectTypeSet(booleanType, false, new ConstantBoolean(true)), + builder.getInt1(true)); + llvm::Value *masked = builder.CreateAnd(boxedVal.value, u64(RT_TAG_MASK)); + return TypedValue(ObjectTypeSet(booleanType, false), + builder.CreateICmpEQ(masked, u64(RT_TAG_SYMBOL), "is_sym")); +} - if (val.type.isBoxedType(symbolType)) { - return unboxSymbol(val); - } +// --- UNBOXING --- - throwInternalInconsistencyException("Only determined types can be unboxed: " + - val.type.toString()); +TypedValue ValueEncoder::unbox(TypedValue val) { + if (!val.type.isBoxedType()) return val; + + if (val.type.isBoxedPointer()) { + return unboxPointer(val); } - TypedValue ValueEncoder::unboxDouble(TypedValue boxedVal) { - if (boxedVal.type.isUnboxedType(doubleType)) - return boxedVal; - return TypedValue( - ObjectTypeSet(doubleType, false), - builder.CreateBitCast(boxedVal.value, types.doubleTy, "unbox_dbl")); + if (val.type.isBoxedType(doubleType)) { + return unboxDouble(val); } - TypedValue ValueEncoder::unboxInt32(TypedValue boxedVal) { - if (boxedVal.type.isUnboxedType(integerType)) - return boxedVal; - return TypedValue( - ObjectTypeSet(integerType, false), - builder.CreateTrunc(boxedVal.value, types.i32Ty, "unbox_int")); + if (val.type.isBoxedType(integerType)) { + return unboxInt32(val); } - TypedValue ValueEncoder::unboxBool(TypedValue boxedVal) { - if (boxedVal.type.isUnboxedType(booleanType)) - return boxedVal; - return TypedValue( - ObjectTypeSet(booleanType, false), - builder.CreateTrunc(boxedVal.value, types.i1Ty, "unbox_bool")); + if (val.type.isBoxedType(booleanType)) { + return unboxBool(val); } - TypedValue ValueEncoder::unboxPointer(TypedValue boxedVal) { - if (boxedVal.type.isUnboxedPointer()) - return boxedVal; - llvm::Value *cleanVal = builder.CreateAnd(boxedVal.value, u64(~RT_TAG_MASK)); - - // Cast back to Pointer - if (word_size == 32) { - llvm::Value *ptr32 = builder.CreateTrunc(cleanVal, types.i32Ty); - return TypedValue( - boxedVal.type.unboxed(), - builder.CreateIntToPtr(ptr32, types.ptrTy, "unbox_ptr")); - } else { - return TypedValue( - boxedVal.type.unboxed(), - builder.CreateIntToPtr(cleanVal, types.ptrTy, "unbox_ptr")); - } + if (val.type.isBoxedType(keywordType)) { + return unboxKeyword(val); } - TypedValue ValueEncoder::unboxKeyword(TypedValue boxedVal) { - if (boxedVal.type.isBoxedType(keywordType)) - return boxedVal; - // Strip the tag and return the ID (usually i32 is enough for enum IDs) - return TypedValue( - ObjectTypeSet(keywordType, false), - builder.CreateTrunc(boxedVal.value, types.i32Ty, "unbox_kw")); + if (val.type.isBoxedType(symbolType)) { + return unboxSymbol(val); } - TypedValue ValueEncoder::unboxSymbol(TypedValue boxedVal) { - if (boxedVal.type.isBoxedType(symbolType)) - return boxedVal; - // Strip the tag and return the ID (usually i32 is enough for enum IDs) + throwInternalInconsistencyException("Only determined types can be unboxed: " + + val.type.toString()); + return val; +} + +TypedValue ValueEncoder::unboxDouble(TypedValue boxedVal) { + if (boxedVal.type.isUnboxedType(doubleType)) + return boxedVal; + return TypedValue( + ObjectTypeSet(doubleType, false), + builder.CreateBitCast(boxedVal.value, types.doubleTy, "unbox_dbl")); +} + +TypedValue ValueEncoder::unboxInt32(TypedValue boxedVal) { + if (boxedVal.type.isUnboxedType(integerType)) + return boxedVal; + return TypedValue( + ObjectTypeSet(integerType, false), + builder.CreateTrunc(boxedVal.value, types.i32Ty, "unbox_int")); +} + +TypedValue ValueEncoder::unboxBool(TypedValue boxedVal) { + if (boxedVal.type.isUnboxedType(booleanType)) + return boxedVal; + return TypedValue( + ObjectTypeSet(booleanType, false), + builder.CreateTrunc(boxedVal.value, types.i1Ty, "unbox_bool")); +} + +TypedValue ValueEncoder::unboxPointer(TypedValue boxedVal) { + if (boxedVal.type.isUnboxedPointer()) + return boxedVal; + llvm::Value *cleanVal = builder.CreateAnd(boxedVal.value, u64(~RT_TAG_MASK)); + + // Cast back to Pointer + if (word_size == 32) { + llvm::Value *ptr32 = builder.CreateTrunc(cleanVal, types.i32Ty); + return TypedValue(boxedVal.type.unboxed(), + builder.CreateIntToPtr(ptr32, types.ptrTy, "unbox_ptr")); + } else { return TypedValue( - ObjectTypeSet(symbolType, false), - builder.CreateTrunc(boxedVal.value, types.i32Ty, "unbox_sym")); + boxedVal.type.unboxed(), + builder.CreateIntToPtr(cleanVal, types.ptrTy, "unbox_ptr")); } +} + +TypedValue ValueEncoder::unboxKeyword(TypedValue boxedVal) { + if (boxedVal.type.isBoxedType(keywordType)) + return boxedVal; + // Strip the tag and return the ID (usually i32 is enough for enum IDs) + return TypedValue( + ObjectTypeSet(keywordType, false), + builder.CreateTrunc(boxedVal.value, types.i32Ty, "unbox_kw")); +} + +TypedValue ValueEncoder::unboxSymbol(TypedValue boxedVal) { + if (boxedVal.type.isBoxedType(symbolType)) + return boxedVal; + // Strip the tag and return the ID (usually i32 is enough for enum IDs) + return TypedValue( + ObjectTypeSet(symbolType, false), + builder.CreateTrunc(boxedVal.value, types.i32Ty, "unbox_sym")); +} } // namespace rt diff --git a/backend-v2/codegen/ValueEncoder.h b/backend-v2/codegen/ValueEncoder.h index 4beef7a0..0003a4d8 100644 --- a/backend-v2/codegen/ValueEncoder.h +++ b/backend-v2/codegen/ValueEncoder.h @@ -1,60 +1,63 @@ #ifndef VALUE_ENCODER_H #define VALUE_ENCODER_H +#include "../RuntimeHeaders.h" +#include "LLVMTypes.h" +#include "TypedValue.h" #include #include #include -#include "TypedValue.h" -#include "LLVMTypes.h" -#include "../RuntimeHeaders.h" namespace rt { /** * NaN boxing. */ - - class ValueEncoder { - llvm::LLVMContext& context; - llvm::IRBuilder<> &builder; - LLVMTypes &types; - int word_size; // 32 or 64 - - // Internal helper - llvm::Value* u64(uint64_t val); - - public: - // Constructor - explicit ValueEncoder(llvm::LLVMContext& ctx, llvm::IRBuilder<>& b, LLVMTypes &t); - - // Boxing (Creating safe 64-bit values) - TypedValue box(TypedValue val); - - TypedValue boxDouble(TypedValue doubleVal); - TypedValue boxInt32(TypedValue int32Val); - TypedValue boxBool(TypedValue boolVal); - TypedValue boxNil(); - TypedValue boxPointer(TypedValue rawPtr); - TypedValue boxKeyword(TypedValue keywordId); - TypedValue boxSymbol(TypedValue symbolId); - - // Type Checking (Returns i1 boolean) - TypedValue isDouble(TypedValue boxedVal); - TypedValue isInt32(TypedValue boxedVal); - TypedValue isBool(TypedValue boxedVal); - TypedValue isNil(TypedValue boxedVal); - TypedValue isPointer(TypedValue boxedVal); - TypedValue isKeyword(TypedValue boxedVal); - TypedValue isSymbol(TypedValue boxedVal); - - // Unboxing (Returns raw type) - TypedValue unbox(TypedValue val); - - TypedValue unboxDouble(TypedValue boxedVal); - TypedValue unboxInt32(TypedValue boxedVal); - TypedValue unboxBool(TypedValue boxedVal); - TypedValue unboxPointer(TypedValue boxedVal); - TypedValue unboxKeyword(TypedValue boxedVal); - TypedValue unboxSymbol(TypedValue boxedVal); + +class ValueEncoder { + llvm::LLVMContext &context; + llvm::IRBuilder<> &builder; + LLVMTypes &types; + int word_size; // 32 or 64 + + // Internal helper + llvm::Value *u64(uint64_t val); + +public: + // Constructor + explicit ValueEncoder(llvm::LLVMContext &ctx, llvm::IRBuilder<> &b, + LLVMTypes &t); + + // Boxing (Creating safe 64-bit values) + TypedValue box(TypedValue val); + + TypedValue boxDouble(TypedValue doubleVal); + TypedValue boxInt32(TypedValue int32Val); + TypedValue boxBool(TypedValue boolVal); + TypedValue boxNil(); + TypedValue boxPointer(TypedValue rawPtr); + TypedValue boxKeyword(TypedValue keywordId); + TypedValue boxSymbol(TypedValue symbolId); + + // Type Checking (Returns i1 boolean) + TypedValue isDouble(TypedValue boxedVal); + TypedValue isInt32(TypedValue boxedVal); + TypedValue isBool(TypedValue boxedVal); + TypedValue isNil(TypedValue boxedVal); + TypedValue isPointer(TypedValue boxedVal); + TypedValue isKeyword(TypedValue boxedVal); + TypedValue isBigInt(TypedValue boxedVal); + TypedValue isRatio(TypedValue boxedVal); + TypedValue isSymbol(TypedValue boxedVal); + + // Unboxing (Returns raw type) + TypedValue unbox(TypedValue val); + + TypedValue unboxDouble(TypedValue boxedVal); + TypedValue unboxInt32(TypedValue boxedVal); + TypedValue unboxBool(TypedValue boxedVal); + TypedValue unboxPointer(TypedValue boxedVal); + TypedValue unboxKeyword(TypedValue boxedVal); + TypedValue unboxSymbol(TypedValue boxedVal); }; } // namespace rt diff --git a/backend-v2/codegen/VariableBindings.h b/backend-v2/codegen/VariableBindings.h index 548fcc6a..4f2a9e34 100644 --- a/backend-v2/codegen/VariableBindings.h +++ b/backend-v2/codegen/VariableBindings.h @@ -1,82 +1,68 @@ #ifndef VARIABLE_BINDINGS_H #define VARIABLE_BINDINGS_H +#include "../RuntimeHeaders.h" +#include "../bridge/Exceptions.h" +#include "TypedValue.h" #include #include #include -#include "TypedValue.h" -#include "../RuntimeHeaders.h" -#include "../bridge/Exceptions.h" - namespace rt { - template - class VariableBindings { - std::vector> variableBindingStack; - public: - // Constructor - VariableBindings() {} - - word_t stackDepth() const { return variableBindingStack.size(); } - - // level -1 means current level - T *find(const std::string &key, word_t level = -1) { - std::unordered_map &levelMap = - variableBindingStack[level == -1 ? (stackDepth() - 1) : level]; - auto result = levelMap.find(key); - if (result == levelMap.end()) return nullptr; - return &(result->second); - } - - // Always sets at the current level - void set(const std::string &key, const T &value) { - if (stackDepth() == 0) - throwInternalInconsistencyException( - "Internal error - the stack has no levels."); - std::unordered_map &levelMap = - variableBindingStack[stackDepth() - 1]; - auto result = levelMap.find(key); - if (result != levelMap.end()) - throwInternalInconsistencyException( - "Internal error - variable: '" + key + - "' is already present on variable stack."); - levelMap[key] = value; - } - - // Creates a new stack level and makes it current - void push() { - variableBindingStack.push_back(std::unordered_map()); - } - - // Pops stack one level - void pop() { - if (stackDepth() == 0) - throwInternalInconsistencyException( - "Internal error - the stack has no levels."); - variableBindingStack.pop_back(); - } - +template class VariableBindings { + std::vector> variableBindingStack; + +public: + // Constructor + VariableBindings() {} + + word_t stackDepth() const { return variableBindingStack.size(); } + + // level -1 means current level + T *find(const std::string &key, word_t level = -1) { + std::unordered_map &levelMap = + variableBindingStack[level == -1 ? (stackDepth() - 1) : level]; + auto result = levelMap.find(key); + if (result == levelMap.end()) + return nullptr; + return &(result->second); + } + + // Always sets at the current level + void set(const std::string &key, const T &value) { + if (stackDepth() == 0) + throwInternalInconsistencyException( + "Internal error - the stack has no levels."); + std::unordered_map &levelMap = + variableBindingStack[stackDepth() - 1]; + auto result = levelMap.find(key); + if (result != levelMap.end()) + throwInternalInconsistencyException( + "Internal error - variable: '" + key + + "' is already present on variable stack."); + levelMap[key] = value; + } + + // Creates a new stack level and makes it current + void push() { + variableBindingStack.push_back(std::unordered_map()); + } + + // Pops stack one level + void pop() { + if (stackDepth() == 0) + throwInternalInconsistencyException( + "Internal error - the stack has no levels."); + variableBindingStack.pop_back(); + } }; } // namespace rt #endif // VALUE_ENCODER_H - - - /* struct LoopInsertionPoint { */ - /* llvm::BasicBlock *block; */ - /* llvm::PHINode *phiNode; */ - /* }; */ - /* std::unordered_map loopInsertPoints; */ - - - - - - - - - - - +/* struct LoopInsertionPoint { */ +/* llvm::BasicBlock *block; */ +/* llvm::PHINode *phiNode; */ +/* }; */ +/* std::unordered_map loopInsertPoints; */ diff --git a/backend-v2/codegen/invoke/InvokeManager.cpp b/backend-v2/codegen/invoke/InvokeManager.cpp index fbae1ed1..db1e7a15 100644 --- a/backend-v2/codegen/invoke/InvokeManager.cpp +++ b/backend-v2/codegen/invoke/InvokeManager.cpp @@ -1,254 +1,164 @@ #include "InvokeManager.h" #include "../../bridge/Exceptions.h" +#include "../../tools/EdnParser.h" +#include "../../types/ConstantBool.h" +#include "../../types/ConstantDouble.h" +#include "../../types/ConstantInteger.h" +#include "../../types/ConstantBigInteger.h" +#include "../../types/ConstantRatio.h" + +#include "llvm/IR/MDBuilder.h" +#include +#include #include +#include using namespace llvm; using namespace std; namespace rt { +// External registration functions +void registerMathIntrinsics(InvokeManager &mgr); +void registerCmpIntrinsics(InvokeManager &mgr); + InvokeManager::InvokeManager(llvm::IRBuilder<> &b, llvm::Module &m, ValueEncoder &v, LLVMTypes &t, ThreadsafeCompilerState &s) - : builder(b), theModule(m), valueEncoder(v), types(t), state(s) { - intrinsics = { - // Arithmetic - {"FAdd", - [](auto &b, auto args) { return b.CreateFAdd(args[0], args[1]); }}, - {"Add", [](auto &b, auto args) { return b.CreateAdd(args[0], args[1]); }}, - {"FSub", - [](auto &b, auto args) { return b.CreateFSub(args[0], args[1]); }}, - {"Sub", [](auto &b, auto args) { return b.CreateSub(args[0], args[1]); }}, - {"FMul", - [](auto &b, auto args) { return b.CreateFMul(args[0], args[1]); }}, - {"Mul", [](auto &b, auto args) { return b.CreateMul(args[0], args[1]); }}, - {"FDiv", - [](auto &b, auto args) { return b.CreateFDiv(args[0], args[1]); }}, - {"Div", - [](auto &b, auto args) { return b.CreateSDiv(args[0], args[1]); }}, - - // Comparisons - {"FCmpOGE", - [](auto &b, auto args) { return b.CreateFCmpOGE(args[0], args[1]); }}, - {"ICmpSGE", - [](auto &b, auto args) { return b.CreateICmpSGE(args[0], args[1]); }}, - {"FCmpOLT", - [](auto &b, auto args) { return b.CreateFCmpOLT(args[0], args[1]); }}, - {"ICmpSLT", - [](auto &b, auto args) { return b.CreateICmpSLT(args[0], args[1]); }}}; + : builder(b), theModule(m), valueEncoder(v), types(t) { + + // Register domain intrinsics + registerMathIntrinsics(*this); + registerCmpIntrinsics(*this); } -ObjectTypeSet InvokeManager::returnValueType(PersistentArrayMap *description) { - RTValue returnValueTypeIndicator = PersistentArrayMap_get( - description, Keyword_create(String_create("returns"))); +// Folding Helpers Implementation - objectType returnValueTypeIndicatorType = getType(returnValueTypeIndicator); - if (returnValueTypeIndicatorType != keywordType && - returnValueTypeIndicatorType != symbolType) { - release(returnValueTypeIndicator); - throwInternalInconsistencyException( - "Return type must be an alias or a symbol"); +mpz_ptr InvokeManager::getZ(const ObjectTypeSet &s) { + auto *c = s.getConstant(); + if (!c) return nullptr; + if (auto *i = dynamic_cast(c)) { + mpz_ptr res = (mpz_ptr)malloc(sizeof(__mpz_struct)); + mpz_init_set_si(res, i->value); + return res; } - - 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"))); - - if (getType(returnValueTypeEnum) != integerType) { - release(returnValueTypeEnum); - throwInternalInconsistencyException( - "Return value type must be an internal type"); + if (auto *b = dynamic_cast(c)) { + mpz_ptr res = (mpz_ptr)malloc(sizeof(__mpz_struct)); + mpz_init_set(res, b->value); + return res; } - - ObjectTypeSet returnValueTypeSet( - (objectType)RT_unboxInt32(returnValueTypeEnum)); - /* Only a formality, integers are not memory managed */ - release(returnValueTypeEnum); - return returnValueTypeSet; + return nullptr; } -bool InvokeManager::checkIntrinsicArgs(PersistentArrayMap *description, - const std::vector &args) { - ThreadsafeRegistry &internalClassRegistry = - state.internalClassRegistry; - - RTValue argsVecRaw = PersistentArrayMap_get( - description, Keyword_create(String_create("args"))); - - if (getType(argsVecRaw) != persistentVectorType) { - release(argsVecRaw); - throwInternalInconsistencyException( - "Intrinsic call: :args is not a vector"); +mpq_ptr InvokeManager::getQ(const ObjectTypeSet &s) { + auto *c = s.getConstant(); + if (!c) return nullptr; + if (auto *i = dynamic_cast(c)) { + mpq_ptr res = (mpq_ptr)malloc(sizeof(__mpq_struct)); + mpq_init(res); + mpq_set_si(res, i->value, 1); + mpq_canonicalize(res); + return res; } - - PersistentVector *argsVec = (PersistentVector *)RT_unboxPtr(argsVecRaw); - - if (PersistentVector_count(argsVec) != args.size()) { - release(argsVecRaw); - return false; + if (auto *b = dynamic_cast(c)) { + mpq_ptr res = (mpq_ptr)malloc(sizeof(__mpq_struct)); + mpq_init(res); + mpq_set_z(res, b->value); + mpq_canonicalize(res); + return res; } - - PersistentVectorIterator it = PersistentVector_iterator(argsVec); - - for (size_t i = 0; i < args.size(); i++) { - RTValue argRaw = PersistentVector_iteratorGet(&it); - auto &arg = args[i]; - objectType argType = getType(argRaw); - if (argType != keywordType && argType != symbolType) { - release(argsVecRaw); - return false; - } - - if (!arg.isDetermined()) { - RTValue anyKeyword = Keyword_create(String_create("any")); - if (equals(argRaw, anyKeyword)) { - release(anyKeyword); - 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) */ - if (!argClass) { - release(argsVecRaw); - 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"))); - - if (!equals(className, argRaw) && !equals(classAlias, argRaw)) { - release(classAlias); - release(className); - release(argsVecRaw); - return false; - } - release(classAlias); - release(className); - PersistentVector_iteratorNext(&it); + if (auto *r = dynamic_cast(c)) { + mpq_ptr res = (mpq_ptr)malloc(sizeof(__mpq_struct)); + mpq_init(res); + mpq_set(res, r->value); + return res; } - release(argsVecRaw); - return true; + return nullptr; } -TypedValue -InvokeManager::generateIntrinsic(RTValue intrinsicDescription, - const std::vector &args) { - std::vector argTypes; - for (auto arg : args) { - argTypes.push_back(arg.type.unboxed()); - } +double InvokeManager::getD(const ObjectTypeSet &s) { + auto *c = s.getConstant(); + if (!c) return 0.0; + if (auto *d = dynamic_cast(c)) return d->value; + if (auto *i = dynamic_cast(c)) return (double)i->value; + if (auto *b = dynamic_cast(c)) return mpz_get_d(b->value); + if (auto *r = dynamic_cast(c)) return mpq_get_d(r->value); + return 0.0; +} - if (getType(intrinsicDescription) != persistentArrayMapType) { - release(intrinsicDescription); - throwInternalInconsistencyException( - "Intrinsic call: description is not a map"); +ObjectTypeSet InvokeManager::createZ(mpz_ptr val) { + if (mpz_fits_slong_p(val)) { + return ObjectTypeSet(integerType, false, new ConstantInteger(mpz_get_si(val))); } - PersistentArrayMap *description = - (PersistentArrayMap *)RT_unboxPtr(intrinsicDescription); - Ptr_retain(description); + return ObjectTypeSet(bigIntegerType, false, new ConstantBigInteger(val)); +} - 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); - if (!checkIntrinsicArgs(description, argTypes)) { - throwInternalInconsistencyException("Args do not match"); - } +ObjectTypeSet InvokeManager::createQ(mpq_ptr val) { + mpq_canonicalize(val); + if (mpz_cmp_si(mpq_denref(val), 1) == 0) { + return createZ(mpq_numref(val)); + } + return ObjectTypeSet(ratioType, false, new ConstantRatio(val)); +} - 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"); +TypedValue InvokeManager::generateIntrinsic(const IntrinsicDescription &id, + const vector &args) { + if (id.type == CallType::Intrinsic) { + auto it = intrinsics.find(id.symbol); + if (it == intrinsics.end()) { + throwInternalInconsistencyException("Intrinsic '" + id.symbol + + "' does not exist."); } - String *compactifiedIntrinsicName = - String_compactify((String *)RT_unboxPtr(intrinsicName)); - std::string stringIntrinsicName(String_c_str(compactifiedIntrinsicName)); - Ptr_release(compactifiedIntrinsicName); - - 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")); + std::vector argVals; + for (auto &arg : args) + argVals.push_back(valueEncoder.unbox(arg).value); + return TypedValue(id.returnType, it->second(builder, argVals)); + } else if (id.type == CallType::Call) { + return invokeRuntime(id.symbol, &id.returnType, id.argTypes, args); + } - if (equals(callType, intriniscKeyword)) { - auto block = intrinsics.find(stringIntrinsicName); - if (block == intrinsics.end()) { - release(intriniscKeyword); - release(callKeyword); - throwInternalInconsistencyException( - "Intrinsic '" + stringIntrinsicName + "' does not exist."); - } - std::vector argVals; - for (auto arg : args) { - argVals.push_back(valueEncoder.unbox(arg).value); - } + throwInternalInconsistencyException("Unsupported call type"); +} - release(intriniscKeyword); - release(callKeyword); - Ptr_release(description); - return TypedValue(returnValueTypeSet, block->second(builder, argVals)); - } else if (equals(callType, callKeyword)) { - release(intriniscKeyword); - release(callKeyword); - Ptr_release(description); - return invokeRuntime(stringIntrinsicName, &returnValueTypeSet, argTypes, - args); - } else { - release(intriniscKeyword); - release(callType); - throwInternalInconsistencyException("Unknown intrinsic type."); +ObjectTypeSet InvokeManager::foldIntrinsic(const IntrinsicDescription &id, + const vector &args) { + auto it = typeIntrinsics.find(id.symbol); + if (it != typeIntrinsics.end()) { + ObjectTypeSet folded = it->second(args); + if (folded.isDetermined() && folded.getConstant()) { + return folded; } - } catch (...) { - Ptr_release(description); - throw; } + return id.returnType; } -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"); +TypedValue InvokeManager::invokeRuntime(const std::string &fname, + const ObjectTypeSet *retValType, + const std::vector &argTypes, + const std::vector &args, + const bool isVariadic) { std::vector llvmTypes; - for (auto &t : argTypes) { - llvmTypes.push_back(types.typeForType(t)); + for (auto &at : argTypes) { + if (at.isBoxedType()) + llvmTypes.push_back(types.RT_valueTy); + else + llvmTypes.push_back(types.typeForType(ObjectTypeSet(at.determinedType()))); } - if (!isVariadic && argTypes.size() != args.size()) - throwInternalInconsistencyException("Internal error: Wrong arg count"); - FunctionType *functionType = FunctionType::get( - retValType ? types.typeForType(*retValType) : types.voidTy, llvmTypes, - isVariadic); + retValType && !retValType->isBoxedType() + ? types.typeForType(ObjectTypeSet(retValType->determinedType())) + : types.RT_valueTy, + llvmTypes, isVariadic); + - Function *toCall = - theModule.getFunction(fname) - ?: Function::Create(functionType, Function::ExternalLinkage, fname, + Function *toCall = theModule.getFunction(fname); + if (!toCall) { + toCall = Function::Create(functionType, Function::ExternalLinkage, fname, theModule); + } vector argVals; for (size_t i = 0; i < args.size(); i++) { @@ -274,8 +184,10 @@ TypedValue InvokeManager::invokeRuntime( argVals.push_back(valueEncoder.box(arg).value); } } + return TypedValue( retValType ? *retValType : ObjectTypeSet::all(), builder.CreateCall(toCall, argVals, std::string("call_") + fname)); } + } // namespace rt diff --git a/backend-v2/codegen/invoke/InvokeManager.h b/backend-v2/codegen/invoke/InvokeManager.h index ff464c2e..a541b859 100644 --- a/backend-v2/codegen/invoke/InvokeManager.h +++ b/backend-v2/codegen/invoke/InvokeManager.h @@ -1,31 +1,49 @@ #ifndef INVOKE_MANAGER_H #define INVOKE_MANAGER_H -#include "../../state/ThreadsafeCompilerState.h" -#include "../LLVMTypes.h" -#include "../TypedValue.h" -#include "../ValueEncoder.h" -#include "bytecode.pb.h" #include #include #include #include #include +#include +#include +#include +#include -using namespace clojure::rt::protobuf::bytecode; -using IntrinsicCall = std::function &, - std::vector)>; +#include "../TypedValue.h" +#include "../ValueEncoder.h" +#include "../../types/ObjectTypeSet.h" namespace rt { +class ThreadsafeCompilerState; +class IntrinsicDescription; + +using IntrinsicCall = std::function &, + std::vector)>; +using TypeIntrinsicCall = + std::function &)>; + class InvokeManager { + friend void registerMathIntrinsics(InvokeManager &mgr); + friend void registerCmpIntrinsics(InvokeManager &mgr); + private: llvm::IRBuilder<> &builder; llvm::Module &theModule; ValueEncoder &valueEncoder; LLVMTypes &types; std::unordered_map intrinsics; - ThreadsafeCompilerState &state; + std::unordered_map typeIntrinsics; + + // Folding Helpers + mpz_ptr getZ(const ObjectTypeSet &t); + mpq_ptr getQ(const ObjectTypeSet &t); + double getD(const ObjectTypeSet &t); + ObjectTypeSet createZ(mpz_ptr val); + ObjectTypeSet createQ(mpq_ptr val); + public: explicit InvokeManager(llvm::IRBuilder<> &b, llvm::Module &m, ValueEncoder &v, @@ -37,13 +55,14 @@ class InvokeManager { const std::vector &args, const bool isVariadic = false); - TypedValue generateIntrinsic(RTValue intrinsicDescription, + TypedValue generateIntrinsic(const IntrinsicDescription &id, const std::vector &args); - bool checkIntrinsicArgs(PersistentArrayMap *description, - const std::vector &args); - ObjectTypeSet returnValueType(PersistentArrayMap *description); + + ObjectTypeSet foldIntrinsic(const IntrinsicDescription &id, + const std::vector &args); }; + } // namespace rt #endif // INVOKE_MANAGER_H diff --git a/backend-v2/codegen/invoke/InvokeManager_Cmp.cpp b/backend-v2/codegen/invoke/InvokeManager_Cmp.cpp new file mode 100644 index 00000000..c460ad11 --- /dev/null +++ b/backend-v2/codegen/invoke/InvokeManager_Cmp.cpp @@ -0,0 +1,355 @@ +#include "InvokeManager.h" +#include "../../types/ConstantBool.h" +#include "../../types/ConstantDouble.h" +#include "../../types/ConstantInteger.h" +#include "../../types/ConstantBigInteger.h" +#include "../../types/ConstantRatio.h" + +using namespace llvm; +using namespace std; + +namespace rt { + +void registerCmpIntrinsics(InvokeManager &mgr) { + auto &typeIntrinsics = mgr.typeIntrinsics; + auto &intrinsics = mgr.intrinsics; + + // --- Folding --- + + typeIntrinsics["ICmpSGE"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *i1 = dynamic_cast(c1); + auto *i2 = dynamic_cast(c2); + if (i1 && i2) return ObjectTypeSet(booleanType, false, new ConstantBoolean(i1->value >= i2->value)); + return ObjectTypeSet(booleanType, false); + }; + typeIntrinsics["ICmpSGT"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *i1 = dynamic_cast(c1); + auto *i2 = dynamic_cast(c2); + if (i1 && i2) return ObjectTypeSet(booleanType, false, new ConstantBoolean(i1->value > i2->value)); + return ObjectTypeSet(booleanType, false); + }; + typeIntrinsics["ICmpSLT"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *i1 = dynamic_cast(c1); + auto *i2 = dynamic_cast(c2); + if (i1 && i2) return ObjectTypeSet(booleanType, false, new ConstantBoolean(i1->value < i2->value)); + return ObjectTypeSet(booleanType, false); + }; + typeIntrinsics["ICmpSLE"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *i1 = dynamic_cast(c1); + auto *i2 = dynamic_cast(c2); + if (i1 && i2) return ObjectTypeSet(booleanType, false, new ConstantBoolean(i1->value <= i2->value)); + return ObjectTypeSet(booleanType, false); + }; + typeIntrinsics["ICmpEQ"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *i1 = dynamic_cast(c1); + auto *i2 = dynamic_cast(c2); + if (i1 && i2) return ObjectTypeSet(booleanType, false, new ConstantBoolean(i1->value == i2->value)); + return ObjectTypeSet(booleanType, false); + }; + + typeIntrinsics["FCmpOGE"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *d1 = dynamic_cast(c1); + auto *d2 = dynamic_cast(c2); + if (d1 && d2) return ObjectTypeSet(booleanType, false, new ConstantBoolean(d1->value >= d2->value)); + return ObjectTypeSet(booleanType, false); + }; + typeIntrinsics["FCmpOGT"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *d1 = dynamic_cast(c1); + auto *d2 = dynamic_cast(c2); + if (d1 && d2) return ObjectTypeSet(booleanType, false, new ConstantBoolean(d1->value > d2->value)); + return ObjectTypeSet(booleanType, false); + }; + typeIntrinsics["FCmpOLT"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *d1 = dynamic_cast(c1); + auto *d2 = dynamic_cast(c2); + if (d1 && d2) return ObjectTypeSet(booleanType, false, new ConstantBoolean(d1->value < d2->value)); + return ObjectTypeSet(booleanType, false); + }; + typeIntrinsics["FCmpOLE"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *d1 = dynamic_cast(c1); + auto *d2 = dynamic_cast(c2); + if (d1 && d2) return ObjectTypeSet(booleanType, false, new ConstantBoolean(d1->value <= d2->value)); + return ObjectTypeSet(booleanType, false); + }; + typeIntrinsics["FCmpOEQ"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *d1 = dynamic_cast(c1); + auto *d2 = dynamic_cast(c2); + if (d1 && d2) return ObjectTypeSet(booleanType, false, new ConstantBoolean(d1->value == d2->value)); + return ObjectTypeSet(booleanType, false); + }; + + // BigInt Comparisons Folding + auto regZCmp = [&](const string &symbol, auto op) { + typeIntrinsics[symbol] = [&mgr, op](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet(booleanType, false); + mpz_ptr p1 = mgr.getZ(args[0]); + mpz_ptr p2 = mgr.getZ(args[1]); + if (p1 && p2) { + bool res = op(p1, p2); + mpz_clear(p1); mpz_clear(p2); + free(p1); free(p2); + return ObjectTypeSet(booleanType, false, new ConstantBoolean(res)); + } + if (p1) { mpz_clear(p1); free(p1); } + if (p2) { mpz_clear(p2); free(p2); } + return ObjectTypeSet(booleanType, false); + }; + }; + regZCmp("BigInteger_gte", [](mpz_t a, mpz_t b) { return mpz_cmp(a, b) >= 0; }); + regZCmp("BigInteger_gt", [](mpz_t a, mpz_t b) { return mpz_cmp(a, b) > 0; }); + regZCmp("BigInteger_lt", [](mpz_t a, mpz_t b) { return mpz_cmp(a, b) < 0; }); + regZCmp("BigInteger_lte", [](mpz_t a, mpz_t b) { return mpz_cmp(a, b) <= 0; }); + regZCmp("BigInteger_equiv", [](mpz_t a, mpz_t b) { return mpz_cmp(a, b) == 0; }); + + // Ratio Comparisons Folding + auto regQCmp = [&](const string &symbol, auto op) { + typeIntrinsics[symbol] = [&mgr, op](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet(booleanType, false); + mpq_ptr p1 = mgr.getQ(args[0]); + mpq_ptr p2 = mgr.getQ(args[1]); + if (p1 && p2) { + bool res = op(p1, p2); + mpq_clear(p1); mpq_clear(p2); + free(p1); free(p2); + return ObjectTypeSet(booleanType, false, new ConstantBoolean(res)); + } + if (p1) { mpq_clear(p1); free(p1); } + if (p2) { mpq_clear(p2); free(p2); } + return ObjectTypeSet(booleanType, false); + }; + }; + regQCmp("Ratio_gte", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) >= 0; }); + regQCmp("Ratio_gt", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) > 0; }); + regQCmp("Ratio_lt", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) < 0; }); + regQCmp("Ratio_lte", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) <= 0; }); + regQCmp("Ratio_equiv", [](mpq_t a, mpq_t b) { return mpq_equal(a, b); }); + + // Mixed-type Comparison Folding + auto regMixedDCmp = [&](const string &symbol, auto op) { + typeIntrinsics[symbol] = [&mgr, op](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet(booleanType, false); + double d1 = mgr.getD(args[0]); + double d2 = mgr.getD(args[1]); + return ObjectTypeSet(booleanType, false, new ConstantBoolean(op(d1, d2))); + }; + }; + regMixedDCmp("ICmpSGE_ID", [](double a, double b) { return a >= b; }); + regMixedDCmp("ICmpSGE_DI", [](double a, double b) { return a >= b; }); + regMixedDCmp("ICmpSGT_ID", [](double a, double b) { return a > b; }); + regMixedDCmp("ICmpSGT_DI", [](double a, double b) { return a > b; }); + regMixedDCmp("ICmpSLT_ID", [](double a, double b) { return a < b; }); + regMixedDCmp("ICmpSLT_DI", [](double a, double b) { return a < b; }); + regMixedDCmp("ICmpSLE_ID", [](double a, double b) { return a <= b; }); + regMixedDCmp("ICmpSLE_DI", [](double a, double b) { return a <= b; }); + regMixedDCmp("FCmpOEQ_ID", [](double a, double b) { return a == b; }); + regMixedDCmp("FCmpOEQ_DI", [](double a, double b) { return a == b; }); + regMixedDCmp("FCmpOGE_BD", [](double a, double b) { return a >= b; }); + regMixedDCmp("FCmpOGE_DB", [](double a, double b) { return a >= b; }); + regMixedDCmp("FCmpOGT_BD", [](double a, double b) { return a > b; }); + regMixedDCmp("FCmpOGT_DB", [](double a, double b) { return a > b; }); + regMixedDCmp("FCmpOLT_BD", [](double a, double b) { return a < b; }); + regMixedDCmp("FCmpOLT_DB", [](double a, double b) { return a < b; }); + regMixedDCmp("FCmpOLE_BD", [](double a, double b) { return a <= b; }); + regMixedDCmp("FCmpOLE_DB", [](double a, double b) { return a <= b; }); + regMixedDCmp("FCmpOEQ_BD", [](double a, double b) { return a == b; }); + regMixedDCmp("FCmpOEQ_DB", [](double a, double b) { return a == b; }); + regMixedDCmp("FCmpOGE_RD", [](double a, double b) { return a >= b; }); + regMixedDCmp("FCmpOGE_DR", [](double a, double b) { return a >= b; }); + regMixedDCmp("FCmpOGT_RD", [](double a, double b) { return a > b; }); + regMixedDCmp("FCmpOGT_DR", [](double a, double b) { return a > b; }); + regMixedDCmp("FCmpOLT_RD", [](double a, double b) { return a < b; }); + regMixedDCmp("FCmpOLT_DR", [](double a, double b) { return a < b; }); + regMixedDCmp("FCmpOLE_RD", [](double a, double b) { return a <= b; }); + regMixedDCmp("FCmpOLE_DR", [](double a, double b) { return a <= b; }); + regMixedDCmp("FCmpOEQ_RD", [](double a, double b) { return a == b; }); + regMixedDCmp("FCmpOEQ_DR", [](double a, double b) { return a == b; }); + + auto regMixedQCmp = [&](const string &symbol, auto op) { + typeIntrinsics[symbol] = [&mgr, op](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet(booleanType, false); + mpq_ptr p1 = mgr.getQ(args[0]); + mpq_ptr p2 = mgr.getQ(args[1]); + if (p1 && p2) { + bool res = op(p1, p2); + mpq_clear(p1); mpq_clear(p2); + free(p1); free(p2); + return ObjectTypeSet(booleanType, false, new ConstantBoolean(res)); + } + if (p1) { mpq_clear(p1); free(p1); } + if (p2) { mpq_clear(p2); free(p2); } + return ObjectTypeSet(booleanType, false); + }; + }; + regMixedQCmp("FCmpOGE_BR", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) >= 0; }); + regMixedQCmp("FCmpOGE_RB", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) >= 0; }); + regMixedQCmp("FCmpOGT_BR", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) > 0; }); + regMixedQCmp("FCmpOGT_RB", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) > 0; }); + regMixedQCmp("FCmpOLT_BR", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) < 0; }); + regMixedQCmp("FCmpOLT_RB", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) < 0; }); + regMixedQCmp("FCmpOLE_BR", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) <= 0; }); + regMixedQCmp("FCmpOLE_RB", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) <= 0; }); + regMixedQCmp("FCmpOGE_IR", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) >= 0; }); + regMixedQCmp("FCmpOGE_RI", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) >= 0; }); + regMixedQCmp("FCmpOGT_IR", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) > 0; }); + regMixedQCmp("FCmpOGT_RI", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) > 0; }); + regMixedQCmp("FCmpOLT_IR", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) < 0; }); + regMixedQCmp("FCmpOLT_RI", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) < 0; }); + regMixedQCmp("FCmpOLE_IR", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) <= 0; }); + regMixedQCmp("FCmpOLE_RI", [](mpq_t a, mpq_t b) { return mpq_cmp(a, b) <= 0; }); + + + // --- Codegen --- + + intrinsics["FCmpOGE"] = [](auto &b, auto args) { + return b.CreateFCmpOGE(args[0], args[1]); + }; + intrinsics["ICmpSGE"] = [](auto &b, auto args) { + return b.CreateICmpSGE(args[0], args[1]); + }; + intrinsics["FCmpOGT"] = [](auto &b, auto args) { + return b.CreateFCmpOGT(args[0], args[1]); + }; + intrinsics["ICmpSGT"] = [](auto &b, auto args) { + return b.CreateICmpSGT(args[0], args[1]); + }; + intrinsics["FCmpOLT"] = [](auto &b, auto args) { + return b.CreateFCmpOLT(args[0], args[1]); + }; + intrinsics["FCmpOLE"] = [](auto &b, auto args) { + return b.CreateFCmpOLE(args[0], args[1]); + }; + intrinsics["ICmpSLT"] = [](auto &b, auto args) { + return b.CreateICmpSLT(args[0], args[1]); + }; + intrinsics["ICmpSLE"] = [](auto &b, auto args) { + return b.CreateICmpSLE(args[0], args[1]); + }; + intrinsics["ICmpEQ"] = [](auto &b, auto args) { + return b.CreateICmpEQ(args[0], args[1]); + }; + intrinsics["FCmpOEQ"] = [](auto &b, auto args) { + return b.CreateFCmpOEQ(args[0], args[1]); + }; + + // BigInt/Ratio Comparisons Codegen + auto regZCmpCodegen = [&mgr, &intrinsics](const string &symbol, const string &fnName) { + intrinsics[symbol] = [&mgr, fnName](auto &b, auto args) { + ObjectTypeSet z(bigIntegerType); + ObjectTypeSet boolT(booleanType); + return mgr.invokeRuntime(fnName, &boolT, {z, z}, {TypedValue(z, args[0]), TypedValue(z, args[1])}).value; + }; + }; + regZCmpCodegen("BigInteger_gte", "BigInteger_gte"); + regZCmpCodegen("BigInteger_gt", "BigInteger_gt"); + regZCmpCodegen("BigInteger_lt", "BigInteger_lt"); + regZCmpCodegen("BigInteger_lte", "BigInteger_lte"); + regZCmpCodegen("BigInteger_equiv", "BigInteger_equiv"); + + auto regQCmpCodegen = [&mgr, &intrinsics](const string &symbol, const string &fnName) { + intrinsics[symbol] = [&mgr, fnName](auto &b, auto args) { + ObjectTypeSet q(ratioType); + ObjectTypeSet boolT(booleanType); + return mgr.invokeRuntime(fnName, &boolT, {q, q}, {TypedValue(q, args[0]), TypedValue(q, args[1])}).value; + }; + }; + regQCmpCodegen("Ratio_gte", "Ratio_gte"); + regQCmpCodegen("Ratio_gt", "Ratio_gt"); + regQCmpCodegen("Ratio_lt", "Ratio_lt"); + regQCmpCodegen("Ratio_lte", "Ratio_lte"); + regQCmpCodegen("Ratio_equiv", "Ratio_equiv"); + + // Mixed BigInt/Double Comparison Codegen + auto makeDB = [&mgr, &intrinsics](const string &opSymbol) { + return [&mgr, &intrinsics, opSymbol](auto &b, auto args) { + ObjectTypeSet doubleTypeSet(doubleType); + TypedValue v2 = mgr.invokeRuntime("BigInteger_toDouble", &doubleTypeSet, + {ObjectTypeSet(bigIntegerType)}, + {TypedValue(ObjectTypeSet(bigIntegerType), args[1])}); + return intrinsics[opSymbol](b, {args[0], v2.value}); + }; + }; + + auto makeBD = [&mgr, &intrinsics](const string &opSymbol) { + return [&mgr, &intrinsics, opSymbol](auto &b, auto args) { + ObjectTypeSet doubleTypeSet(doubleType); + TypedValue v1 = mgr.invokeRuntime("BigInteger_toDouble", &doubleTypeSet, + {ObjectTypeSet(bigIntegerType)}, + {TypedValue(ObjectTypeSet(bigIntegerType), args[0])}); + return intrinsics[opSymbol](b, {v1.value, args[1]}); + }; + }; + + intrinsics["FCmpOGE_BD"] = makeBD("FCmpOGE"); + intrinsics["FCmpOGE_DB"] = makeDB("FCmpOGE"); + intrinsics["FCmpOGT_BD"] = makeBD("FCmpOGT"); + intrinsics["FCmpOGT_DB"] = makeDB("FCmpOGT"); + intrinsics["FCmpOLT_BD"] = makeBD("FCmpOLT"); + intrinsics["FCmpOLT_DB"] = makeDB("FCmpOLT"); + intrinsics["FCmpOLE_BD"] = makeBD("FCmpOLE"); + intrinsics["FCmpOLE_DB"] = makeDB("FCmpOLE"); + intrinsics["FCmpOEQ_BD"] = makeBD("FCmpOEQ"); + intrinsics["FCmpOEQ_DB"] = makeDB("FCmpOEQ"); + + auto makeDR = [&mgr, &intrinsics](const string &opSymbol) { + return [&mgr, &intrinsics, opSymbol](auto &b, auto args) { + ObjectTypeSet doubleTypeSet(doubleType); + TypedValue v2 = mgr.invokeRuntime("Ratio_toDouble", &doubleTypeSet, + {ObjectTypeSet(ratioType)}, + {TypedValue(ObjectTypeSet(ratioType), args[1])}); + return intrinsics[opSymbol](b, {args[0], v2.value}); + }; + }; + + auto makeRD = [&mgr, &intrinsics](const string &opSymbol) { + return [&mgr, &intrinsics, opSymbol](auto &b, auto args) { + ObjectTypeSet doubleTypeSet(doubleType); + TypedValue v1 = mgr.invokeRuntime("Ratio_toDouble", &doubleTypeSet, + {ObjectTypeSet(ratioType)}, + {TypedValue(ObjectTypeSet(ratioType), args[0])}); + return intrinsics[opSymbol](b, {v1.value, args[1]}); + }; + }; + + intrinsics["FCmpOGE_RD"] = makeRD("FCmpOGE"); + intrinsics["FCmpOGE_DR"] = makeDR("FCmpOGE"); + intrinsics["FCmpOGT_RD"] = makeRD("FCmpOGT"); + intrinsics["FCmpOGT_DR"] = makeDR("FCmpOGT"); + intrinsics["FCmpOLT_RD"] = makeRD("FCmpOLT"); + intrinsics["FCmpOLT_DR"] = makeDR("FCmpOLT"); + intrinsics["FCmpOLE_RD"] = makeRD("FCmpOLE"); + intrinsics["FCmpOLE_DR"] = makeDR("FCmpOLE"); + intrinsics["FCmpOEQ_RD"] = makeRD("FCmpOEQ"); + intrinsics["FCmpOEQ_DR"] = makeDR("FCmpOEQ"); +} + +} // namespace rt diff --git a/backend-v2/codegen/invoke/InvokeManager_Math.cpp b/backend-v2/codegen/invoke/InvokeManager_Math.cpp new file mode 100644 index 00000000..d84f7d5c --- /dev/null +++ b/backend-v2/codegen/invoke/InvokeManager_Math.cpp @@ -0,0 +1,553 @@ +#include "InvokeManager.h" +#include "../../types/ConstantBool.h" +#include "../../types/ConstantDouble.h" +#include "../../types/ConstantInteger.h" +#include "../../types/ConstantBigInteger.h" +#include "../../types/ConstantRatio.h" +#include "llvm/IR/MDBuilder.h" + +using namespace llvm; +using namespace std; + +namespace rt { + +void registerMathIntrinsics(InvokeManager &mgr) { + auto &typeIntrinsics = mgr.typeIntrinsics; + auto &intrinsics = mgr.intrinsics; + auto &types = mgr.types; + auto &theModule = mgr.theModule; + + // --- Folding --- + + typeIntrinsics["Add"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *i1 = dynamic_cast(c1); + auto *i2 = dynamic_cast(c2); + if (i1 && i2) return ObjectTypeSet(integerType, false, new ConstantInteger(i1->value + i2->value)); + return ObjectTypeSet(integerType, false); + }; + typeIntrinsics["Sub"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *i1 = dynamic_cast(c1); + auto *i2 = dynamic_cast(c2); + if (i1 && i2) return ObjectTypeSet(integerType, false, new ConstantInteger(i1->value - i2->value)); + return ObjectTypeSet(integerType, false); + }; + typeIntrinsics["Mul"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *i1 = dynamic_cast(c1); + auto *i2 = dynamic_cast(c2); + if (i1 && i2) return ObjectTypeSet(integerType, false, new ConstantInteger(i1->value * i2->value)); + return ObjectTypeSet(integerType, false); + }; + typeIntrinsics["Div"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *i1 = dynamic_cast(c1); + auto *i2 = dynamic_cast(c2); + if (i1 && i2 && i2->value != 0) return ObjectTypeSet(integerType, false, new ConstantInteger(i1->value / i2->value)); + return ObjectTypeSet(integerType, false); + }; + + typeIntrinsics["FAdd"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *d1 = dynamic_cast(c1); + auto *d2 = dynamic_cast(c2); + if (d1 && d2) return ObjectTypeSet(doubleType, false, new ConstantDouble(d1->value + d2->value)); + return ObjectTypeSet(doubleType, false); + }; + typeIntrinsics["FSub"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *d1 = dynamic_cast(c1); + auto *d2 = dynamic_cast(c2); + if (d1 && d2) return ObjectTypeSet(doubleType, false, new ConstantDouble(d1->value - d2->value)); + return ObjectTypeSet(doubleType, false); + }; + typeIntrinsics["FMul"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *d1 = dynamic_cast(c1); + auto *d2 = dynamic_cast(c2); + if (d1 && d2) return ObjectTypeSet(doubleType, false, new ConstantDouble(d1->value * d2->value)); + return ObjectTypeSet(doubleType, false); + }; + typeIntrinsics["FDiv"] = [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + auto *c1 = args[0].getConstant(); + auto *c2 = args[1].getConstant(); + auto *d1 = dynamic_cast(c1); + auto *d2 = dynamic_cast(c2); + if (d1 && d2 && d2->value != 0.0) return ObjectTypeSet(doubleType, false, new ConstantDouble(d1->value / d2->value)); + return ObjectTypeSet(doubleType, false); + }; + + // BigInt pure math folding + auto regZMath = [&](const string &symbol, auto op) { + typeIntrinsics[symbol] = [&mgr, op](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet(bigIntegerType, false); + mpz_ptr p1 = mgr.getZ(args[0]); + mpz_ptr p2 = mgr.getZ(args[1]); + if (p1 && p2) { + mpz_t res; + mpz_init(res); + op(res, p1, p2); + auto ret = mgr.createZ(res); + mpz_clear(p1); mpz_clear(p2); mpz_clear(res); + free(p1); free(p2); + return ret; + } + if (p1) { mpz_clear(p1); free(p1); } + if (p2) { mpz_clear(p2); free(p2); } + return ObjectTypeSet(bigIntegerType, false); + }; + }; + regZMath("BigInteger_add", mpz_add); + regZMath("BigInteger_sub", mpz_sub); + regZMath("BigInteger_mul", mpz_mul); + + typeIntrinsics["BigInteger_div"] = [&mgr](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet::dynamicType(); + mpq_ptr p1 = mgr.getQ(args[0]); + mpq_ptr p2 = mgr.getQ(args[1]); + if (p1 && p2) { + if (mpq_sgn(p2) == 0) { + mpq_clear(p1); mpq_clear(p2); free(p1); free(p2); + return ObjectTypeSet::dynamicType(); + } + mpq_t res; + mpq_init(res); + mpq_div(res, p1, p2); + auto ret = mgr.createQ(res); + mpq_clear(p1); mpq_clear(p2); mpq_clear(res); + free(p1); free(p2); + return ret; + } + if (p1) { mpq_clear(p1); free(p1); } + if (p2) { mpq_clear(p2); free(p2); } + return ObjectTypeSet::dynamicType(); + }; + typeIntrinsics["Integer_div"] = typeIntrinsics["BigInteger_div"]; + + // Ratio pure math folding + auto regQMath = [&](const string &symbol, auto op) { + typeIntrinsics[symbol] = [&mgr, op](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet(ratioType, false); + mpq_ptr p1 = mgr.getQ(args[0]); + mpq_ptr p2 = mgr.getQ(args[1]); + if (p1 && p2) { + mpq_t res; + mpq_init(res); + op(res, p1, p2); + auto ret = mgr.createQ(res); + mpq_clear(p1); mpq_clear(p2); mpq_clear(res); + free(p1); free(p2); + return ret; + } + if (p1) { mpq_clear(p1); free(p1); } + if (p2) { mpq_clear(p2); free(p2); } + return ObjectTypeSet(ratioType, false); + }; + }; + regQMath("Ratio_add", mpq_add); + regQMath("Ratio_sub", mpq_sub); + regQMath("Ratio_mul", mpq_mul); + regQMath("Ratio_div", [](mpq_t r, mpq_t a, mpq_t b) { + if (mpq_sgn(b) != 0) mpq_div(r, a, b); + }); + + // Mixed type folding helpers + auto regMixZ = [&](const string &symbol, auto op) { + typeIntrinsics[symbol] = [&mgr, op](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet(bigIntegerType, false); + mpz_ptr p1 = mgr.getZ(args[0]); + mpz_ptr p2 = mgr.getZ(args[1]); + if (p1 && p2) { + mpz_t res; + mpz_init(res); + op(res, p1, p2); + auto ret = mgr.createZ(res); + mpz_clear(p1); mpz_clear(p2); mpz_clear(res); + free(p1); free(p2); + return ret; + } + if (p1) { mpz_clear(p1); free(p1); } + if (p2) { mpz_clear(p2); free(p2); } + return ObjectTypeSet(bigIntegerType, false); + }; + }; + regMixZ("Add_IB", mpz_add); regMixZ("Add_BI", mpz_add); + regMixZ("Sub_IB", mpz_sub); regMixZ("Sub_BI", mpz_sub); + regMixZ("Mul_IB", mpz_mul); regMixZ("Mul_BI", mpz_mul); + regMixZ("Div_IB", [](mpz_t r, mpz_t a, mpz_t b) { if (mpz_sgn(b) != 0) mpz_tdiv_q(r, a, b); }); + regMixZ("Div_BI", [](mpz_t r, mpz_t a, mpz_t b) { if (mpz_sgn(b) != 0) mpz_tdiv_q(r, a, b); }); + + auto regMixD = [&](const string &symbol, auto op) { + typeIntrinsics[symbol] = [&mgr, op](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet(doubleType, false); + double d1 = mgr.getD(args[0]); + double d2 = mgr.getD(args[1]); + return ObjectTypeSet(doubleType, false, new ConstantDouble(op(d1, d2))); + }; + }; + regMixD("Add_ID", [](double a, double b) { return a + b; }); + regMixD("Add_DI", [](double a, double b) { return a + b; }); + regMixD("Sub_ID", [](double a, double b) { return a - b; }); + regMixD("Sub_DI", [](double a, double b) { return a - b; }); + regMixD("Mul_ID", [](double a, double b) { return a * b; }); + regMixD("Mul_DI", [](double a, double b) { return a * b; }); + regMixD("Div_ID", [](double a, double b) { return a / b; }); + regMixD("Div_DI", [](double a, double b) { return a / b; }); + regMixD("Add_BD", [](double a, double b) { return a + b; }); + regMixD("Add_DB", [](double a, double b) { return a + b; }); + regMixD("Sub_BD", [](double a, double b) { return a - b; }); + regMixD("Sub_DB", [](double a, double b) { return a - b; }); + regMixD("Mul_BD", [](double a, double b) { return a * b; }); + regMixD("Mul_DB", [](double a, double b) { return a * b; }); + regMixD("Div_BD", [](double a, double b) { return a / b; }); + regMixD("Div_DB", [](double a, double b) { return a / b; }); + regMixD("Add_RD", [](double a, double b) { return a + b; }); + regMixD("Add_DR", [](double a, double b) { return a + b; }); + regMixD("Sub_RD", [](double a, double b) { return a - b; }); + regMixD("Sub_DR", [](double a, double b) { return a - b; }); + regMixD("Mul_RD", [](double a, double b) { return a * b; }); + regMixD("Mul_DR", [](double a, double b) { return a * b; }); + regMixD("Div_RD", [](double a, double b) { return a / b; }); + regMixD("Div_DR", [](double a, double b) { return a / b; }); + + auto regMixQ = [&](const string &symbol, auto op) { + typeIntrinsics[symbol] = [&mgr, op](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) return ObjectTypeSet(ratioType, false); + mpq_ptr p1 = mgr.getQ(args[0]); + mpq_ptr p2 = mgr.getQ(args[1]); + if (p1 && p2) { + mpq_t res; + mpq_init(res); + op(res, p1, p2); + auto ret = mgr.createQ(res); + mpq_clear(p1); mpq_clear(p2); mpq_clear(res); + free(p1); free(p2); + return ret; + } + if (p1) { mpq_clear(p1); free(p1); } + if (p2) { mpq_clear(p2); free(p2); } + return ObjectTypeSet(ratioType, false); + }; + }; + regMixQ("Add_BR", mpq_add); regMixQ("Add_RB", mpq_add); + regMixQ("Sub_BR", mpq_sub); regMixQ("Sub_RB", mpq_sub); + regMixQ("Mul_BR", mpq_mul); regMixQ("Mul_RB", mpq_mul); + regMixQ("Div_BR", [](mpq_t r, mpq_t a, mpq_t b) { if (mpq_sgn(b) != 0) mpq_div(r, a, b); }); + regMixQ("Div_RB", [](mpq_t r, mpq_t a, mpq_t b) { if (mpq_sgn(b) != 0) mpq_div(r, a, b); }); + regMixQ("Add_IR", mpq_add); regMixQ("Add_RI", mpq_add); + regMixQ("Sub_IR", mpq_sub); regMixQ("Sub_RI", mpq_sub); + regMixQ("Mul_IR", mpq_mul); regMixQ("Mul_RI", mpq_mul); + regMixQ("Div_IR", [](mpq_t r, mpq_t a, mpq_t b) { if (mpq_sgn(b) != 0) mpq_div(r, a, b); }); + regMixQ("Div_RI", [](mpq_t r, mpq_t a, mpq_t b) { if (mpq_sgn(b) != 0) mpq_div(r, a, b); }); + + + // --- Codegen --- + + intrinsics["FAdd"] = [](auto &b, auto args) { + return b.CreateFAdd(args[0], args[1]); + }; + + auto createCheckedOp = [&mgr, &theModule, &types](const std::string &llvmIntrinsic, + const std::string &errorMessage) { + return [&mgr, &theModule, &types, llvmIntrinsic, errorMessage](llvm::IRBuilder<> &b, + std::vector args) { + Function *fn = Intrinsic::getDeclaration(&theModule, + llvmIntrinsic == "sadd" ? Intrinsic::sadd_with_overflow : Intrinsic::ssub_with_overflow, + {b.getInt32Ty()}); + Value *resStruct = b.CreateCall(fn, args); + Value *res = b.CreateExtractValue(resStruct, {0}); + Value *overflow = b.CreateExtractValue(resStruct, {1}); + + BasicBlock *insertBB = b.GetInsertBlock(); + Function *currentFn = insertBB->getParent(); + + BasicBlock *overflowBB = BasicBlock::Create(theModule.getContext(), "overflow", currentFn); + BasicBlock *continueBB = BasicBlock::Create(theModule.getContext(), "no_overflow", currentFn); + + MDBuilder MDB(theModule.getContext()); + MDNode *Weights = MDB.createBranchWeights(1, 1000000); + b.CreateCondBr(overflow, overflowBB, continueBB, Weights); + + b.SetInsertPoint(overflowBB); + FunctionType *throwFnTy = FunctionType::get(types.voidTy, {mgr.types.ptrTy}, false); + FunctionCallee throwFn = theModule.getOrInsertFunction("throwArithmeticException_C", throwFnTy); + Value *msg = b.CreateGlobalString(errorMessage); + b.CreateCall(throwFn, {msg}); + b.CreateUnreachable(); + + b.SetInsertPoint(continueBB); + return res; + }; + }; + + intrinsics["Add"] = createCheckedOp("sadd", "Integer overflow"); + intrinsics["Sub"] = createCheckedOp("ssub", "Integer overflow"); + intrinsics["FSub"] = [](auto &b, auto args) { + return b.CreateFSub(args[0], args[1]); + }; + intrinsics["FMul"] = [](auto &b, auto args) { + return b.CreateFMul(args[0], args[1]); + }; + intrinsics["Mul"] = [](auto &b, auto args) { + return b.CreateMul(args[0], args[1]); + }; + intrinsics["FDiv"] = [](auto &b, auto args) { + return b.CreateFDiv(args[0], args[1]); + }; + intrinsics["Div"] = [](auto &b, auto args) { + return b.CreateSDiv(args[0], args[1]); + }; + + // BigInt/Ratio Codegen + auto regZCodegen = [&mgr, &intrinsics](const string &symbol, const string &fnName) { + intrinsics[symbol] = [&mgr, fnName](auto &b, auto args) { + ObjectTypeSet z(bigIntegerType); + return mgr.invokeRuntime(fnName, &z, {z, z}, {TypedValue(z, args[0]), TypedValue(z, args[1])}).value; + }; + }; + regZCodegen("BigInteger_add", "BigInteger_add"); + regZCodegen("BigInteger_sub", "BigInteger_sub"); + regZCodegen("BigInteger_mul", "BigInteger_mul"); + intrinsics["BigInteger_div"] = [&mgr](auto &b, auto args) { + ObjectTypeSet z(bigIntegerType); + ObjectTypeSet anyT = ObjectTypeSet::all(); + return mgr.invokeRuntime("BigInteger_div", &anyT, {z, z}, {TypedValue(z, args[0]), TypedValue(z, args[1])}).value; + }; + intrinsics["Integer_div"] = [&mgr](auto &b, auto args) { + ObjectTypeSet i(integerType); + ObjectTypeSet anyT = ObjectTypeSet::all(); + return mgr.invokeRuntime("Integer_div", &anyT, {i, i}, {TypedValue(i, args[0]), TypedValue(i, args[1])}).value; + }; + + auto regQCodegen = [&mgr, &intrinsics](const string &symbol, const string &fnName) { + intrinsics[symbol] = [&mgr, fnName](auto &b, auto args) { + ObjectTypeSet q(ratioType); + ObjectTypeSet anyT = ObjectTypeSet::all(); + return mgr.invokeRuntime(fnName, &anyT, {q, q}, {TypedValue(q, args[0]), TypedValue(q, args[1])}).value; + }; + }; + regQCodegen("Ratio_add", "Ratio_add"); + regQCodegen("Ratio_sub", "Ratio_sub"); + regQCodegen("Ratio_mul", "Ratio_mul"); + regQCodegen("Ratio_div", "Ratio_div"); + + // Mixed-type Arithmetic Codegen + intrinsics["Add_ID"] = [&mgr](auto &b, auto args) { + Value *v1 = b.CreateSIToFP(args[0], mgr.types.doubleTy, "conv"); + return b.CreateFAdd(v1, args[1]); + }; + intrinsics["Add_DI"] = [&mgr](auto &b, auto args) { + Value *v2 = b.CreateSIToFP(args[1], mgr.types.doubleTy, "conv"); + return b.CreateFAdd(args[0], v2); + }; + intrinsics["Sub_ID"] = [&mgr](auto &b, auto args) { + Value *v1 = b.CreateSIToFP(args[0], mgr.types.doubleTy, "conv"); + return b.CreateFSub(v1, args[1]); + }; + intrinsics["Sub_DI"] = [&mgr](auto &b, auto args) { + Value *v2 = b.CreateSIToFP(args[1], mgr.types.doubleTy, "conv"); + return b.CreateFSub(args[0], v2); + }; + intrinsics["Mul_ID"] = [&mgr](auto &b, auto args) { + Value *v1 = b.CreateSIToFP(args[0], mgr.types.doubleTy, "conv"); + return b.CreateFMul(v1, args[1]); + }; + intrinsics["Mul_DI"] = [&mgr](auto &b, auto args) { + Value *v2 = b.CreateSIToFP(args[1], mgr.types.doubleTy, "conv"); + return b.CreateFMul(args[0], v2); + }; + intrinsics["Div_ID"] = [&mgr](auto &b, auto args) { + Value *v1 = b.CreateSIToFP(args[0], mgr.types.doubleTy, "conv"); + return b.CreateFDiv(v1, args[1]); + }; + intrinsics["Div_DI"] = [&mgr](auto &b, auto args) { + Value *v2 = b.CreateSIToFP(args[1], mgr.types.doubleTy, "conv"); + return b.CreateFDiv(args[0], v2); + }; + + auto makeIB = [&mgr](const std::string &opSymbol) { + return [&mgr, opSymbol](auto &b, auto args) { + auto retType = ObjectTypeSet(bigIntegerType); + TypedValue v1 = mgr.invokeRuntime( + "BigInteger_createFromInt", &retType, {ObjectTypeSet(integerType)}, + {TypedValue(ObjectTypeSet(integerType), args[0])}); + return mgr.invokeRuntime( + opSymbol, &retType, + {ObjectTypeSet(bigIntegerType), ObjectTypeSet(bigIntegerType)}, + {v1, TypedValue(ObjectTypeSet(bigIntegerType), args[1])}) + .value; + }; + }; + + auto makeBI = [&mgr](const std::string &opSymbol) { + return [&mgr, opSymbol](auto &b, auto args) { + auto retType = ObjectTypeSet(bigIntegerType); + TypedValue v2 = mgr.invokeRuntime( + "BigInteger_createFromInt", &retType, {ObjectTypeSet(integerType)}, + {TypedValue(ObjectTypeSet(integerType), args[1])}); + return mgr.invokeRuntime( + opSymbol, &retType, + {ObjectTypeSet(bigIntegerType), ObjectTypeSet(bigIntegerType)}, + {TypedValue(ObjectTypeSet(bigIntegerType), args[0]), v2}) + .value; + }; + }; + + intrinsics["Add_IB"] = makeIB("BigInteger_add"); + intrinsics["Add_BI"] = makeBI("BigInteger_add"); + intrinsics["Sub_IB"] = makeIB("BigInteger_sub"); + intrinsics["Sub_BI"] = makeBI("BigInteger_sub"); + intrinsics["Mul_IB"] = makeIB("BigInteger_mul"); + intrinsics["Mul_BI"] = makeBI("BigInteger_mul"); + intrinsics["Div_IB"] = makeIB("BigInteger_div"); + intrinsics["Div_BI"] = makeBI("BigInteger_div"); + + auto makeDB = [&mgr, &intrinsics](const std::string &opSymbol) { + return [&mgr, &intrinsics, opSymbol](auto &b, auto args) { + ObjectTypeSet doubleTypeSet(doubleType); + TypedValue v2 = + mgr.invokeRuntime("BigInteger_toDouble", &doubleTypeSet, + {ObjectTypeSet(bigIntegerType)}, + {TypedValue(ObjectTypeSet(bigIntegerType), args[1])}); + return intrinsics[opSymbol](b, {args[0], v2.value}); + }; + }; + + auto makeBD = [&mgr, &intrinsics](const std::string &opSymbol) { + return [&mgr, &intrinsics, opSymbol](auto &b, auto args) { + ObjectTypeSet doubleTypeSet(doubleType); + TypedValue v1 = + mgr.invokeRuntime("BigInteger_toDouble", &doubleTypeSet, + {ObjectTypeSet(bigIntegerType)}, + {TypedValue(ObjectTypeSet(bigIntegerType), args[0])}); + return intrinsics[opSymbol](b, {v1.value, args[1]}); + }; + }; + + intrinsics["Add_BD"] = makeBD("FAdd"); + intrinsics["Add_DB"] = makeDB("FAdd"); + intrinsics["Sub_BD"] = makeBD("FSub"); + intrinsics["Sub_DB"] = makeDB("FSub"); + intrinsics["Mul_BD"] = makeBD("FMul"); + intrinsics["Mul_DB"] = makeBD("FMul"); + intrinsics["Div_BD"] = makeBD("FDiv"); + intrinsics["Div_DB"] = makeDB("FDiv"); + + auto makeDR = [&mgr, &intrinsics](const std::string &opSymbol) { + return [&mgr, &intrinsics, opSymbol](auto &b, auto args) { + ObjectTypeSet doubleTypeSet(doubleType); + TypedValue v2 = mgr.invokeRuntime( + "Ratio_toDouble", &doubleTypeSet, {ObjectTypeSet(ratioType)}, + {TypedValue(ObjectTypeSet(ratioType), args[1])}); + return intrinsics[opSymbol](b, {args[0], v2.value}); + }; + }; + + auto makeRD = [&mgr, &intrinsics](const std::string &opSymbol) { + return [&mgr, &intrinsics, opSymbol](auto &b, auto args) { + ObjectTypeSet doubleTypeSet(doubleType); + TypedValue v1 = mgr.invokeRuntime( + "Ratio_toDouble", &doubleTypeSet, {ObjectTypeSet(ratioType)}, + {TypedValue(ObjectTypeSet(ratioType), args[0])}); + return intrinsics[opSymbol](b, {v1.value, args[1]}); + }; + }; + intrinsics["Add_RD"] = makeRD("FAdd"); + intrinsics["Add_DR"] = makeDR("FAdd"); + intrinsics["Sub_RD"] = makeRD("FSub"); + intrinsics["Sub_DR"] = makeDR("FSub"); + intrinsics["Mul_RD"] = makeRD("FMul"); + intrinsics["Mul_DR"] = makeDR("FMul"); + intrinsics["Div_RD"] = makeRD("FDiv"); + intrinsics["Div_DR"] = makeDR("FDiv"); + + auto makeRB = [&mgr](const std::string &opSymbol) { + return [&mgr, opSymbol](auto &b, auto args) { + ObjectTypeSet retType(ratioType); + ObjectTypeSet bigIntTypeSet(bigIntegerType); + ObjectTypeSet allTypeSet = ObjectTypeSet::all(); + TypedValue v2 = + mgr.invokeRuntime("Ratio_createFromBigInteger", &retType, {bigIntTypeSet}, + {TypedValue(bigIntTypeSet, args[1])}); + return mgr.invokeRuntime(opSymbol, &allTypeSet, {retType, retType}, + {TypedValue(retType, args[0]), v2}) + .value; + }; + }; + + auto makeBR = [&mgr](const std::string &opSymbol) { + return [&mgr, opSymbol](auto &b, auto args) { + ObjectTypeSet retType(ratioType); + ObjectTypeSet bigIntTypeSet(bigIntegerType); + ObjectTypeSet allTypeSet = ObjectTypeSet::all(); + TypedValue v1 = + mgr.invokeRuntime("Ratio_createFromBigInteger", &retType, {bigIntTypeSet}, + {TypedValue(bigIntTypeSet, args[0])}); + return mgr.invokeRuntime(opSymbol, &allTypeSet, {retType, retType}, + {v1, TypedValue(retType, args[1])}) + .value; + }; + }; + intrinsics["Add_BR"] = makeBR("Ratio_add"); + intrinsics["Add_RB"] = makeRB("Ratio_add"); + intrinsics["Sub_BR"] = makeBR("Ratio_sub"); + intrinsics["Sub_RB"] = makeRB("Ratio_sub"); + intrinsics["Mul_BR"] = makeBR("Ratio_mul"); + intrinsics["Mul_RB"] = makeRB("Ratio_mul"); + intrinsics["Div_BR"] = makeBR("Ratio_div"); + intrinsics["Div_RB"] = makeRB("Ratio_div"); + + auto makeRI = [&mgr](const std::string &opSymbol) { + return [&mgr, opSymbol](auto &b, auto args) { + ObjectTypeSet retType(ratioType); + ObjectTypeSet intTypeSet(integerType); + ObjectTypeSet allTypeSet = ObjectTypeSet::all(); + TypedValue v2 = + mgr.invokeRuntime("Ratio_createFromInt", &retType, {intTypeSet}, + {TypedValue(intTypeSet, args[1])}); + return mgr.invokeRuntime(opSymbol, &allTypeSet, {retType, retType}, + {TypedValue(retType, args[0]), v2}) + .value; + }; + }; + + auto makeIR = [&mgr](const std::string &opSymbol) { + return [&mgr, opSymbol](auto &b, auto args) { + ObjectTypeSet retType(ratioType); + ObjectTypeSet intTypeSet(integerType); + ObjectTypeSet allTypeSet = ObjectTypeSet::all(); + TypedValue v1 = + mgr.invokeRuntime("Ratio_createFromInt", &retType, {intTypeSet}, + {TypedValue(intTypeSet, args[0])}); + return mgr.invokeRuntime(opSymbol, &allTypeSet, {retType, retType}, + {v1, TypedValue(retType, args[1])}) + .value; + }; + }; + intrinsics["Add_IR"] = makeIR("Ratio_add"); + intrinsics["Add_RI"] = makeRI("Ratio_add"); + intrinsics["Sub_IR"] = makeIR("Ratio_sub"); + intrinsics["Sub_RI"] = makeRI("Ratio_sub"); + intrinsics["Mul_IR"] = makeIR("Ratio_mul"); + intrinsics["Mul_RI"] = makeRI("Ratio_mul"); + intrinsics["Div_IR"] = makeIR("Ratio_div"); + intrinsics["Div_RI"] = makeRI("Ratio_div"); +} + +} // namespace rt diff --git a/backend-v2/codegen/ops/ConstNode.cpp b/backend-v2/codegen/ops/ConstNode.cpp index ee5ecd62..73e74e63 100644 --- a/backend-v2/codegen/ops/ConstNode.cpp +++ b/backend-v2/codegen/ops/ConstNode.cpp @@ -71,6 +71,7 @@ TypedValue CodeGen::codegen(const Node &node, const ConstNode &subnode, // node); // break; case keywordType: + retVal = dynamicConstructor.createKeyword( (name[0] == ':' ? name.substr(1) : name).c_str()); memoryManagement.dynamicRetain(retVal); @@ -95,6 +96,7 @@ TypedValue CodeGen::codegen(const Node &node, const ConstNode &subnode, // APInt(64, (uint64_t) var, false)), ptrT); dynamicRetain(retVal); break; // } case persistentListType: + case persistentVectorType: case persistentVectorNodeType: case concurrentHashMapType: @@ -114,6 +116,11 @@ ObjectTypeSet CodeGen::getType(const Node &node, const ConstNode &subnode, switch (subnode.type()) { case ConstNode_ConstType_constTypeNumber: + if (subnode.val().find('/') != string::npos) { + return ObjectTypeSet(ratioType, true, new ConstantRatio(subnode.val())) + .restriction(typeRestrictions); + } + if (node.tag() == "clojure.lang.Ratio" || node.otag() == "clojure.lang.Ratio" || node.tag() == "class clojure.lang.Ratio" || @@ -132,17 +139,35 @@ ObjectTypeSet CodeGen::getType(const Node &node, const ConstNode &subnode, } if (node.tag() == "double" || node.otag() == "double" || - node.tag() == "class java.lang.Double") { + node.tag() == "class java.lang.Double" || subnode.val().find('.') != string::npos) { return ObjectTypeSet(doubleType, false, new ConstantDouble(stod(subnode.val()))) .restriction(typeRestrictions); } if (node.tag() == "long" || node.otag() == "long" || - node.tag() == "class java.lang.Long") { - return ObjectTypeSet(integerType, false, - new ConstantInteger(stoi(subnode.val()))) - .restriction(typeRestrictions); + node.tag() == "class java.lang.Long" || node.tag() == "") { + try { + size_t end; + long long val = stoll(subnode.val(), &end); + if (end == subnode.val().size()) { + if (val >= INT32_MIN && val <= INT32_MAX) { + return ObjectTypeSet(integerType, false, + new ConstantInteger((int32_t)val)) + .restriction(typeRestrictions); + } else { + return ObjectTypeSet(bigIntegerType, true, + new ConstantBigInteger(subnode.val())) + .restriction(typeRestrictions); + } + } + } catch (...) { + if (!subnode.val().empty() && (isdigit(subnode.val()[0]) || subnode.val()[0] == '-')) { + return ObjectTypeSet(bigIntegerType, true, + new ConstantBigInteger(subnode.val())) + .restriction(typeRestrictions); + } + } } throwCodeGenerationException( @@ -170,6 +195,7 @@ ObjectTypeSet CodeGen::getType(const Node &node, const ConstNode &subnode, // case ConstNode_ConstType_constTypeVar: // return ObjectTypeSet(varType).restriction(typeRestrictions); case ConstNode_ConstType_constTypeType: + case ConstNode_ConstType_constTypeRecord: case ConstNode_ConstType_constTypeMap: case ConstNode_ConstType_constTypeVector: diff --git a/backend-v2/codegen/ops/IfNode.cpp b/backend-v2/codegen/ops/IfNode.cpp new file mode 100644 index 00000000..5f157003 --- /dev/null +++ b/backend-v2/codegen/ops/IfNode.cpp @@ -0,0 +1,352 @@ +#include "../CodeGen.h" + +using namespace std; +using namespace llvm; + +namespace rt { + +static Value *toTruthyCondition(LLVMContext &TheContext, IRBuilder<> &Builder, + ValueEncoder &valueEncoder, Value *boxedVal) { + TypedValue boxed(ObjectTypeSet::all().boxed(), boxedVal); + Value *isNil = valueEncoder.isNil(boxed).value; + + Function *currentFn = Builder.GetInsertBlock()->getParent(); + BasicBlock *notNilBB = + BasicBlock::Create(TheContext, "cond_not_nil", currentFn); + BasicBlock *mergeBB = BasicBlock::Create(TheContext, "cond_merge", currentFn); + + Builder.CreateCondBr(isNil, mergeBB, notNilBB); + BasicBlock *nilBB = Builder.GetInsertBlock(); + + Builder.SetInsertPoint(notNilBB); + Value *isBool = valueEncoder.isBool(boxed).value; + BasicBlock *checkTrueBB = + BasicBlock::Create(TheContext, "cond_check_true", currentFn); + + Builder.CreateCondBr(isBool, checkTrueBB, mergeBB); + BasicBlock *notBoolBB = Builder.GetInsertBlock(); + + Builder.SetInsertPoint(checkTrueBB); + Value *boolVal = valueEncoder.unboxBool(boxed).value; + Builder.CreateBr(mergeBB); + BasicBlock *boolBB = Builder.GetInsertBlock(); + + Builder.SetInsertPoint(mergeBB); + PHINode *phi = Builder.CreatePHI(Builder.getInt1Ty(), 3, "truthy_phi"); + phi->addIncoming(Builder.getFalse(), nilBB); + phi->addIncoming(Builder.getTrue(), notBoolBB); + phi->addIncoming(boolVal, boolBB); + + return phi; +} + +TypedValue CodeGen::codegen(const Node &node, const IfNode &subnode, + const ObjectTypeSet &typeRestrictions) { + auto testType = getType(subnode.test(), ObjectTypeSet::all()); + auto thenType = getType(subnode.then(), typeRestrictions); + auto elseType = subnode.has_else_() + ? getType(subnode.else_(), typeRestrictions) + : ObjectTypeSet(nilType); + + thenType = thenType.restriction(typeRestrictions); + elseType = elseType.restriction(typeRestrictions); + + Function *parentFunction = Builder.GetInsertBlock()->getParent(); + + if (!testType.contains(nilType) && !testType.contains(booleanType) && + !testType.isEmpty()) { + if (thenType.isEmpty()) + throwCodeGenerationException( + string("Incorrect type: 'then' branch of if cannot fulfil type " + "restrictions: ") + + typeRestrictions.toString(), + node); + + auto test = codegen(subnode.test(), ObjectTypeSet::all()); + if (!test.type.isScalar()) + memoryManagement.dynamicRelease(test); + + return codegen(subnode.then(), typeRestrictions); + } + + if (testType.isDetermined() && testType.determinedType() == nilType) { + /* Test condition is of single type, which is nil - we immediately know test + * fails and else branch is triggered! */ + if (elseType.isEmpty()) + throwCodeGenerationException( + string("Incorrect type: 'else' branch of if cannot fulfil type " + "restrictions: ") + + typeRestrictions.toString(), + node); + + auto test = codegen(subnode.test(), ObjectTypeSet::all()); + if (!test.type.isScalar()) + memoryManagement.dynamicRelease(test); + + return subnode.has_else_() ? codegen(subnode.else_(), typeRestrictions) + : TypedValue(ObjectTypeSet(nilType), + valueEncoder.boxNil().value); + } + + ConstantBoolean *CI = nullptr; + if (testType.getConstant() && + (CI = dynamic_cast(testType.getConstant()))) { + auto test = codegen(subnode.test(), ObjectTypeSet::all()); + if (!test.type.isScalar()) + memoryManagement.dynamicRelease(test); + /* In case of a constant (which often arises from constant folding!) we can + * immediately make a decision on the *compiler* level! */ + bool constCondition = CI->value; + if (constCondition) { + if (thenType.isEmpty()) + throwCodeGenerationException( + string("'then' branch of if expression contains a value that does " + "not match allowed types: ") + + typeRestrictions.toString(), + node); + + return codegen(subnode.then(), typeRestrictions); + } else { + if (elseType.isEmpty()) + throwCodeGenerationException( + string("'else' branch of if expression contains a value that does " + "not match allowed types: ") + + typeRestrictions.toString(), + node); + + if (subnode.has_else_()) + return codegen(subnode.else_(), typeRestrictions); + else + return TypedValue(ObjectTypeSet(nilType), valueEncoder.boxNil().value); + } + } + + /* Standard if condition */ + + auto test = codegen(subnode.test(), ObjectTypeSet::all()); + Value *condValue = test.value; + + if (!condValue) + throwCodeGenerationException(string("Internal error 1"), node); + + if (!test.type.isDetermined()) + condValue = + toTruthyCondition(TheContext, Builder, valueEncoder, test.value); + + else if (test.type.determinedType() == booleanType) + condValue = test.value; + + // create basic blocks + BasicBlock *thenBB = + llvm::BasicBlock::Create(TheContext, "then", parentFunction); + BasicBlock *elseBB = + llvm::BasicBlock::Create(TheContext, "else", parentFunction); + + Builder.CreateCondBr(condValue, thenBB, elseBB); + + // then basic block + Builder.SetInsertPoint(thenBB); + // release test value + if (!test.type.isScalar()) + memoryManagement.dynamicRelease(test); + // then val is that of last value in block + auto thenWithType = thenType.isEmpty() + ? TypedValue(thenType, nullptr) + : codegen(subnode.then(), typeRestrictions); + + // note that the recursive thenExpr codegen call could change block we're + // emitting code into. + thenBB = Builder.GetInsertBlock(); + // Check that block does not return prematurely (in case of recur) + bool thenShortCircuits = + !thenWithType.value || thenWithType.value->getType()->isVoidTy(); + /* we leave the then block incomplete so that we can do a cast if types do not + * match */ + // Builder.CreateBr(mergeBB); + + // else block + Builder.SetInsertPoint(elseBB); + // release test value + if (!test.type.isScalar()) + memoryManagement.dynamicRelease(test); + + // else val is that of last value in block + + TypedValue elseWithType(ObjectTypeSet::empty(), nullptr); + + if (elseType.isEmpty()) { + elseWithType = TypedValue(elseType, nullptr); + } else { + if (subnode.has_else_()) + elseWithType = codegen(subnode.else_(), typeRestrictions); + else + elseWithType = + TypedValue(ObjectTypeSet(nilType), valueEncoder.boxNil().value); + } + + // ditto reasoning to then block + elseBB = Builder.GetInsertBlock(); + bool elseShortCircuits = + !elseWithType.value || elseWithType.value->getType()->isVoidTy(); + bool noShortCircuits = !thenShortCircuits && !elseShortCircuits; + + BasicBlock *mergeBB = nullptr; + if (noShortCircuits) { + mergeBB = llvm::BasicBlock::Create(TheContext, "ifcont", parentFunction); + } else if (thenShortCircuits && !elseShortCircuits) { + mergeBB = elseBB; + } else if (!thenShortCircuits && elseShortCircuits) { + mergeBB = thenBB; + } + + TypedValue returnTypedValue(ObjectTypeSet::empty(), nullptr); + + if (!elseWithType.type.isEmpty() && !thenWithType.type.isEmpty()) { + if (elseWithType.type == thenWithType.type) { + /* we just close off both blocks, same types detected - but const needs to + * be removed */ + returnTypedValue.type = elseWithType.type.removeConst(); + // If there is one short circuit: in short circuiting block ret, so no br + // | in the other block no need to jump (only one predecessor) + if (noShortCircuits) { + Builder.CreateBr(mergeBB); + Builder.SetInsertPoint(thenBB); + Builder.CreateBr(mergeBB); + } + } else { + /* We box any fast types (boxing leaves complex types unaffected) */ + if (!elseShortCircuits) { + elseWithType = valueEncoder.box(elseWithType); + if (noShortCircuits) + Builder.CreateBr(mergeBB); + returnTypedValue.type = + returnTypedValue.type.expansion(elseWithType.type); + } + Builder.SetInsertPoint(thenBB); + if (!thenShortCircuits) { + thenWithType = valueEncoder.box(thenWithType); + if (noShortCircuits) + Builder.CreateBr(mergeBB); + returnTypedValue.type = + returnTypedValue.type.expansion(thenWithType.type); + } + } + } else { + if (elseWithType.type.isEmpty() && thenWithType.type.isEmpty()) + throwCodeGenerationException( + string("Both branches of if cannot fulfil restrictions: ") + + typeRestrictions.toString(), + node); + + /* One of the branches does not fit the requirements. We generate an + * exception if this branch is chosen, but take the other's type in static + * analysis to speed things up */ + if (elseWithType.type.isEmpty()) { + returnTypedValue = thenWithType; + + string errMsg = + string("Runtime exception: 'else' branch of if expression contains a " + "value that does not match allowed types: ") + + typeRestrictions.toString(); + TypedValue msg = dynamicConstructor.createString(errMsg.c_str()); + TypedValue rawPtr = valueEncoder.unboxPointer(msg); + invokeManager.invokeRuntime("throwInternalInconsistencyException_C", + nullptr, {ObjectTypeSet::all()}, {rawPtr}); + + Builder.CreateUnreachable(); + + if (noShortCircuits) + Builder.CreateBr(mergeBB); + Builder.SetInsertPoint(thenBB); + if (noShortCircuits) + Builder.CreateBr(mergeBB); + } + + if (thenWithType.type.isEmpty()) { + returnTypedValue = elseWithType; + + if (noShortCircuits) + Builder.CreateBr(mergeBB); + Builder.SetInsertPoint(thenBB); + + string errMsg = + string("Runtime exception: 'then' branch of if expression contains a " + "value that does not match allowed types: ") + + typeRestrictions.toString(); + TypedValue msg = dynamicConstructor.createString(errMsg.c_str()); + TypedValue rawPtr = valueEncoder.unboxPointer(msg); + invokeManager.invokeRuntime("throwInternalInconsistencyException_C", + nullptr, {ObjectTypeSet::all()}, {rawPtr}); + + Builder.CreateUnreachable(); + + if (noShortCircuits) + Builder.CreateBr(mergeBB); + } + } + + // merge block + if (mergeBB) + Builder.SetInsertPoint(mergeBB); + if (noShortCircuits && thenWithType.value && elseWithType.value) { + llvm::PHINode *phiNode = + Builder.CreatePHI(thenWithType.value->getType(), 2, "iftmp"); + phiNode->addIncoming(thenWithType.value, thenBB); + phiNode->addIncoming(elseWithType.value, elseBB); + returnTypedValue.value = phiNode; + return returnTypedValue; + } + + if (thenWithType.value) + return thenWithType; + return elseWithType; +} + +ObjectTypeSet CodeGen::getType(const Node &node, const IfNode &subnode, + const ObjectTypeSet &typeRestrictions) { + auto testType = getType(subnode.test(), ObjectTypeSet::all()); + auto thenType = getType(subnode.then(), typeRestrictions); + auto elseType = subnode.has_else_() + ? getType(subnode.else_(), typeRestrictions) + : ObjectTypeSet(nilType); + thenType = thenType.restriction(typeRestrictions); + elseType = elseType.restriction(typeRestrictions); + + if (!testType.contains(nilType) && !testType.contains(booleanType)) { + if (thenType.isEmpty()) + throwCodeGenerationException( + string("Incorrect type: 'then' branch of if cannot fulfil type " + "restrictions: ") + + typeRestrictions.toString(), + node); + return thenType; + } + + ConstantBoolean *CI = nullptr; + if (testType.isDetermined()) { /* Test condition is of single type */ + switch (testType.determinedType()) { + case nilType: + if (elseType.isEmpty()) + throwCodeGenerationException( + string("Incorrect type: 'else' branch of if cannot fulfil type " + "restrictions: ") + + typeRestrictions.toString(), + node); + return elseType; + + case booleanType: + if (testType.getConstant() && + (CI = dynamic_cast(testType.getConstant()))) { + if (CI->value) + return thenType; + return elseType; + } + default: + break; + } + } + + return thenType.expansion(elseType); +} + +} // namespace rt diff --git a/backend-v2/codegen/ops/StaticCallNode.cpp b/backend-v2/codegen/ops/StaticCallNode.cpp new file mode 100644 index 00000000..bdf6c382 --- /dev/null +++ b/backend-v2/codegen/ops/StaticCallNode.cpp @@ -0,0 +1,286 @@ +#include "../../tools/EdnParser.h" +#include "../../tools/RTValueWrapper.h" +#include "../../types/ConstantBool.h" +#include "../../types/ConstantDouble.h" +#include "../../types/ConstantInteger.h" +#include "../CodeGen.h" +#include "bridge/Exceptions.h" +#include "bytecode.pb.h" +#include "codegen/TypedValue.h" +#include + +using namespace std; +using namespace llvm; +using namespace clojure::rt::protobuf::bytecode; + +namespace rt { + +TypedValue CodeGen::codegen(const Node &node, const StaticCallNode &subnode, + const ObjectTypeSet &typeRestrictions) { + std::vector argTypes; + std::vector args; + bool allDetermined = true; + for (int i = 0; i < subnode.args_size(); i++) { + auto t = codegen(subnode.args(i), ObjectTypeSet::all()); + if (!t.type.isDetermined()) + allDetermined = false; + argTypes.push_back(t.type.unboxed()); + args.push_back(t); + } + + auto c = subnode.class_(); + auto m = subnode.method(); + string name = (c.rfind("class ", 0) == 0) ? c.substr(6) : c; + + PtrWrapper cls( + this->compilerState.classRegistry.getCurrent(name.c_str())); + if (!cls) { + std::ostringstream oss; + oss << "Class not found: " << name; + throwCodeGenerationException(oss.str(), node); + } + + auto *ext = static_cast(cls->compilerExtension); + if (!ext) { + std::ostringstream oss; + oss << "Class " << name << " does not have compiler metadata"; + throwCodeGenerationException(oss.str(), node); + } + + auto it_method = ext->staticFns.find(m); + if (it_method == ext->staticFns.end()) { + std::ostringstream oss; + oss << "Class " << name << " does not have a static method " << m; + throwCodeGenerationException(oss.str(), node); + } + + const std::vector &versions = it_method->second; + + const IntrinsicDescription *bestMatch = nullptr; + + if (allDetermined) { + // 1. Try exact match + for (const auto &id : versions) { + if (id.argTypes.size() == args.size()) { + bool match = true; + for (size_t i = 0; i < args.size(); i++) { + if (argTypes[i].restriction(id.argTypes[i]).isEmpty()) { + match = false; + break; + } + } + if (match) { + bestMatch = &id; + break; + } + } + } + } + + if (bestMatch) { + std::vector unboxedArgs; + for (size_t i = 0; i < args.size(); i++) { + unboxedArgs.push_back(this->valueEncoder.unbox(args[i])); + } + auto result = + this->invokeManager.generateIntrinsic(*bestMatch, unboxedArgs); + return result; + } + if (allDetermined) { + std::ostringstream oss; + oss << "No matching overload found for " << name << "/" << m + << " with arguments: ["; + for (size_t i = 0; i < argTypes.size(); i++) { + oss << argTypes[i].toString() << (i == argTypes.size() - 1 ? "" : ", "); + } + oss << "]"; + throwCodeGenerationException(oss.str(), node); + } + + throwCodeGenerationException( + "Dynamic dispatch for static calls is not yet supported.", node); + // No compile-time match found or not all determined -> Dynamic Dispatch + // const IntrinsicDescription *generic = nullptr; + // std::vector specialized; + + // for (const auto &id : versions) { + // if (id.argTypes.size() == args.size()) { + // bool isGeneric = true; + // for (const auto &at : id.argTypes) { + // if (!at.isDynamic()) { + // isGeneric = false; + // break; + // } + // } + // if (isGeneric) { + // generic = &id; + // } else { + // specialized.push_back(&id); + // } + // } + // } + + // // If types are determined but NO match found and NO generic fallback -> + // Fail + // // early + // if (allDetermined && !generic) { + // std::ostringstream oss; + // oss << "No matching overload found for " << name << "/" << m + // << " with arguments: ["; + // for (size_t i = 0; i < argTypes.size(); i++) { + // oss << argTypes[i].toString() << (i == argTypes.size() - 1 ? "" : ", + // "); + // } + // oss << "]"; + // throwCodeGenerationException(oss.str(), node); + // } + + // // If we have a generic fallback, limit specialized checks to the first two + // // (fast paths) to save memory + // if (generic && specialized.size() > 2) { + // specialized.resize(2); + // } + + // // Generate dynamic dispatch cascade + // Function *currentFn = this->Builder.GetInsertBlock()->getParent(); + // BasicBlock *mergeBB = + // BasicBlock::Create(this->TheContext, "dispatch_merge", currentFn); + + // struct DispatchCase { + // BasicBlock *bb; + // TypedValue result; + // }; + // std::vector cases; + + // for (const auto *fid : specialized) { + // BasicBlock *thenBB = + // BasicBlock::Create(this->TheContext, "dispatch_then", currentFn); + // BasicBlock *nextBB = + // BasicBlock::Create(this->TheContext, "dispatch_next", currentFn); + + // Value *match = this->Builder.getTrue(); + // for (size_t i = 0; i < args.size(); i++) { + // TypedValue boxed = this->valueEncoder.box(args[i]); + // Value *isType = nullptr; + // objectType target = fid->argTypes[i].determinedType(); + + // if (target == integerType) + // isType = this->valueEncoder.isInt32(boxed).value; + // else if (target == doubleType) + // isType = this->valueEncoder.isDouble(boxed).value; + // else if (target == bigIntegerType) + // isType = this->valueEncoder.isBigInt(boxed).value; + // else if (target == ratioType) + // isType = this->valueEncoder.isRatio(boxed).value; + + // if (isType) { + // match = this->Builder.CreateAnd(match, isType); + // } else { + // match = this->Builder.getFalse(); + // break; + // } + // } + + // this->Builder.CreateCondBr(match, thenBB, nextBB); + // this->Builder.SetInsertPoint(thenBB); + // TypedValue res = this->invokeManager.generateIntrinsic(*fid, args); + // TypedValue boxedRes = this->valueEncoder.box(res); + // cases.push_back({this->Builder.GetInsertBlock(), boxedRes}); + // this->Builder.CreateBr(mergeBB); + // this->Builder.SetInsertPoint(nextBB); + // } + + // // Final fallback + // if (generic) { + // TypedValue genRes = this->invokeManager.generateIntrinsic(*generic, + // args); TypedValue boxedGenRes = this->valueEncoder.box(genRes); + // cases.push_back({this->Builder.GetInsertBlock(), boxedGenRes}); + // this->Builder.CreateBr(mergeBB); + // } else { + // // No match and no generic -> Runtime Exception + // std::stringstream ss; + // ss << "No matching overload found for " << name << "/" << m; + // Value *msgVal = + // this->Builder.CreateGlobalStringPtr(ss.str(), "dispatch_err_msg"); + + // FunctionType *fnTy = + // FunctionType::get(this->types.voidTy, {this->types.ptrTy}, false); + // FunctionCallee fn = this->TheModule->getOrInsertFunction( + // "throwInternalInconsistencyException_C", fnTy); + // this->Builder.CreateCall(fn, {msgVal}); + // this->Builder.CreateUnreachable(); + // } + + // this->Builder.SetInsertPoint(mergeBB); + // if (cases.empty()) { + // // This would happen if there are NO versions at all, but we checked that + // // earlier. + // return TypedValue(ObjectTypeSet::dynamicType(), + // PoisonValue::get(types.RT_valueTy)); + // } + + // PHINode *phi = + // Builder.CreatePHI(types.RT_valueTy, cases.size(), "dispatch_phi"); + // for (auto &c : cases) { + // phi->addIncoming(c.result.value, c.bb); + // } + + // return TypedValue(ObjectTypeSet::dynamicType(), phi); +} + +ObjectTypeSet CodeGen::getType(const Node &node, const StaticCallNode &subnode, + const ObjectTypeSet &typeRestrictions) { + std::vector argTypes; + bool allDetermined = true; + for (int i = 0; i < subnode.args_size(); i++) { + auto t = getType(subnode.args(i), ObjectTypeSet::all()); + if (!t.isDetermined()) + allDetermined = false; + argTypes.push_back(t.unboxed()); + } + + auto c = subnode.class_(); + auto m = subnode.method(); + string name = (c.rfind("class ", 0) == 0) ? c.substr(6) : c; + + PtrWrapper cls(compilerState.classRegistry.getCurrent(name.c_str())); + if (!cls) + return ObjectTypeSet::dynamicType(); + + auto *ext = static_cast(cls->compilerExtension); + if (!ext) + return ObjectTypeSet::dynamicType(); + + auto it_method = ext->staticFns.find(m); + if (it_method == ext->staticFns.end()) + return ObjectTypeSet::dynamicType(); + + const std::vector &versions = it_method->second; + + if (allDetermined) { + // 1. Try exact match + for (const auto &id : versions) { + if (id.argTypes.size() == argTypes.size()) { + bool match = true; + for (size_t i = 0; i < argTypes.size(); i++) { + if (argTypes[i].restriction(id.argTypes[i]).isEmpty()) { + match = false; + break; + } + } + if (match) { + ObjectTypeSet folded = invokeManager.foldIntrinsic(id, argTypes); + if (folded.isDetermined() && folded.getConstant()) { + return folded; + } + return id.returnType; + } + + } + } + } + + return ObjectTypeSet::dynamicType(); +} + +} // namespace rt diff --git a/backend-v2/jit/JITEngine.cpp b/backend-v2/jit/JITEngine.cpp index 082cff91..0b99de05 100644 --- a/backend-v2/jit/JITEngine.cpp +++ b/backend-v2/jit/JITEngine.cpp @@ -1,4 +1,10 @@ #include "JITEngine.h" +#include "bridge/Exceptions.h" +#include +#include +#include +#include +#include namespace rt { @@ -10,11 +16,40 @@ JITEngine::JITEngine(ThreadsafeCompilerState &state, size_t numThreads) llvm::InitializeNativeTargetAsmPrinter(); llvm::InitializeNativeTargetAsmParser(); - auto JITExp = llvm::orc::LLJITBuilder().create(); + auto JITExp = llvm::orc::LLJITBuilder() + .setObjectLinkingLayerCreator( + [&](llvm::orc::ExecutionSession &ES, + const llvm::Triple &TT) { + auto ObjLinkingLayer = + std::make_unique(ES); + + // Add debug info support + auto Registrar = llvm::orc::createJITLoaderGDBRegistrar(ES); + if (Registrar) { + ObjLinkingLayer->addPlugin( + std::make_unique( + ES, std::move(*Registrar))); + } + + return ObjLinkingLayer; + }) + .create(); if (!JITExp) throw std::runtime_error("Failed to init LLJIT"); jit = std::move(*JITExp); + // Set up object transform to capture compiled objects + jit->getObjTransformLayer().setTransform( + [this](std::unique_ptr Obj) + -> llvm::Expected> { + std::lock_guard lock(this->engineMutex); + std::string id = Obj->getBufferIdentifier().str(); + // Store a copy of the buffer content + this->capturedObjectBuffers[id] = llvm::MemoryBuffer::getMemBufferCopy( + Obj->getBuffer(), id); + return std::move(Obj); + }); + // Expose symbols from the current process to the JIT. // This is required on Linux to find runtime functions. auto &DL = jit->getDataLayout(); @@ -27,52 +62,95 @@ 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, moduleName, - printModule]() -> llvm::orc::ExecutorAddr { - auto codeGenerator = CodeGen(moduleName, threadsafeState); - auto fName = codeGenerator.codegenTopLevel(AST); - - auto result = std::move(codeGenerator).release(); - auto context = std::move(result.context); - auto module = std::move(result.module); - auto constants = std::move(result.constants); - - // 4. Optimize WITHOUT LOCK - // if (level != llvm::OptimizationLevel::O0) { - // this->optimize(*mod, Level); - // } - - // --- TUTAJ WYPISUJEMY IR --- - if (module && printModule) { - llvm::outs() << "\n--- Generated LLVM IR for: " << moduleName << " ---\n"; - module->print(llvm::outs(), - nullptr); // Wypisuje IR do standardowego wyjścia LLVM - llvm::outs() << "------------------------------------------\n"; - llvm::outs().flush(); // Wymuszamy opróżnienie bufora, żeby tekst nie - // zniknął przy błędzie - } - // --------------------------- - - llvm::orc::ThreadSafeModule TSM(std::move(module), *context); - - { - std::lock_guard lock(engineMutex); - auto RT = jit->getMainJITDylib().createResourceTracker(); - - // ORC takes ownership of TSM and the Context inside it - if (auto Err = jit->addIRModule(RT, std::move(TSM))) - throw std::runtime_error("JIT Link Error"); - - functionTrackers[moduleName] = RT; - moduleConstants[moduleName] = std::move(constants); - } - - // 6. Return executable address - auto Sym = jit->lookup(fName); - if (!Sym) - throw std::runtime_error("Symbol Lookup Failed"); - return *Sym; - }); + return pool.enqueue( + [this, AST, moduleName, printModule]() -> llvm::orc::ExecutorAddr { + auto codeGenerator = CodeGen(moduleName, threadsafeState); + + std::string fName; + try { + fName = codeGenerator.codegenTopLevel(AST); + } catch (...) { + auto result = std::move(codeGenerator).release(); + for (auto c : result.constants) { + release(c); + } + throw; + } + + auto result = std::move(codeGenerator).release(); + auto context = std::move(result.context); + auto module = std::move(result.module); + auto constants = std::move(result.constants); + + // 4. Optimize WITHOUT LOCK + // if (level != llvm::OptimizationLevel::O0) { + // this->optimize(*mod, Level); + // } + + // --- TUTAJ WYPISUJEMY IR --- + if (module && printModule) { + llvm::outs() << "\n=== Generated LLVM IR for: '" << moduleName + << "' ===\n"; + module->print(llvm::outs(), + nullptr); // Wypisuje IR do standardowego wyjścia LLVM + llvm::outs() << "=============================================\n"; + llvm::outs().flush(); // Wymuszamy opróżnienie bufora, żeby tekst nie + // zniknął przy błędzie + } + // --------------------------- + + llvm::orc::ThreadSafeModule TSM(std::move(module), *context); + + { + std::lock_guard lock(engineMutex); + auto RT = jit->getMainJITDylib().createResourceTracker(); + + // ORC takes ownership of TSM and the Context inside it + if (auto Err = jit->addIRModule(RT, std::move(TSM))) + throwInternalInconsistencyException("JIT Link Error"); + + functionTrackers[moduleName] = RT; + moduleConstants[moduleName] = std::move(constants); + } + + // 6. Return executable address + auto Sym = jit->lookup(fName); + if (!Sym) + throwInternalInconsistencyException("Symbol Lookup Failed: " + fName); + + // Register for stack trace resolution + { + std::lock_guard lock(this->engineMutex); + + // Try to find the captured object buffer. ORC might append suffixes. + auto it = this->capturedObjectBuffers.find(moduleName); + if (it == this->capturedObjectBuffers.end()) { + // Try common ORC suffixes if exact match fails + it = this->capturedObjectBuffers.find(moduleName + "-jitted-objectbuffer"); + } + if (it == this->capturedObjectBuffers.end()) { + // Fallback: search for any key containing moduleName + for (auto searchIt = this->capturedObjectBuffers.begin(); searchIt != this->capturedObjectBuffers.end(); ++searchIt) { + if (searchIt->first.find(moduleName) != std::string::npos) { + it = searchIt; + break; + } + } + } + + if (it != this->capturedObjectBuffers.end()) { + registerJitFunction_C(Sym->getValue(), 4096, fName.c_str(), + it->second->getBufferStart(), + it->second->getBufferSize()); + this->capturedObjectBuffers.erase(it); + } else { + registerJitFunction_C(Sym->getValue(), 4096, fName.c_str(), nullptr, + 0); + } + } + + return *Sym; + }); } void JITEngine::invalidate(const std::string &name) { diff --git a/backend-v2/jit/JITEngine.h b/backend-v2/jit/JITEngine.h index 613b5799..78df6888 100644 --- a/backend-v2/jit/JITEngine.h +++ b/backend-v2/jit/JITEngine.h @@ -11,6 +11,7 @@ #include "llvm/ExecutionEngine/Orc/Core.h" #include "llvm/ExecutionEngine/Orc/LLJIT.h" #include "llvm/ExecutionEngine/Orc/ThreadSafeModule.h" +#include "llvm/ExecutionEngine/Orc/ObjectTransformLayer.h" #include "llvm/ExecutionEngine/SectionMemoryManager.h" // LLVM Optimization (New Pass Manager) @@ -42,6 +43,7 @@ class JITEngine { std::mutex engineMutex; std::map functionTrackers; std::map> moduleConstants; + std::map> capturedObjectBuffers; ThreadsafeCompilerState &threadsafeState; public: diff --git a/backend-v2/main.cpp b/backend-v2/main.cpp index 24ffd829..37ad6de2 100644 --- a/backend-v2/main.cpp +++ b/backend-v2/main.cpp @@ -37,9 +37,14 @@ int main(int argc, char *argv[]) { cout << "Please specify the filename for compilation" << endl; return -1; } + int retVal = -1; GOOGLE_PROTOBUF_VERIFY_VERSION; + MemoryState initialMemoryState; + captureMemoryState(&initialMemoryState); + initialise_memory(); + clojure::rt::protobuf::bytecode::Programme astInterfaces; { fstream interfacesInput("rt-protocols.cljb", ios::in | ios::binary); @@ -67,8 +72,6 @@ int main(int argc, char *argv[]) { } } - initialise_memory(); - try { rt::ThreadsafeCompilerState state; rt::JITEngine engine(state); @@ -89,25 +92,43 @@ int main(int argc, char *argv[]) { state.storeInternalClasses(classes); - // release(intrinsics); + auto f = engine.compileAST(astRoot.nodes(0), "__root", + llvm::OptimizationLevel::O0, true); + cout << "Compiling!!!" << endl; - // auto f = engine.compileAST(astRoot.nodes(0), "__root", - // llvm::OptimizationLevel::O0); cout << "Compiling!!!" << endl; - // printReferenceCounts(); + RTValue whaat = f.get().toPtr()(); + String *s = toString(whaat); + s = String_compactify(s); - // RTValue whaat = res(); - // printReferenceCounts(); - // String *s = toString(whaat); - // s = String_compactify(s); - - // cout << "Result" << endl; - // cout << std::string(String_c_str(s)) << endl; - // cout << "!!!Result" << endl; - // Ptr_release(s); - // printReferenceCounts(); + cout << "========== Result ==========" << endl; + cout << std::string(String_c_str(s)) << endl; + cout << "========== /Result ==========" << endl; + Ptr_release(s); + retVal = 0; } catch (rt::LanguageException e) { cout << rt::getExceptionString(e) << endl; } - return 0; + RuntimeInterface_cleanup(); + + if (strstr(BUILD_TYPE, "Debug")) { + MemoryState finalMemoryState; + captureMemoryState(&finalMemoryState); + bool leaked = false; + for (int i = 0; i < 256; i++) { + if (finalMemoryState.counts[i] != initialMemoryState.counts[i]) { + if (!leaked) { + printf("\n========== Memory Leak Detected ==========\n"); + printReferenceCounts(); + leaked = true; + } + printf("Type %d: expected %lu, got %lu\n", i + 1, + initialMemoryState.counts[i], finalMemoryState.counts[i]); + } + } + if (leaked) { + exit(-1); + } + } + return retVal; } diff --git a/backend-v2/runtime/BigInteger.c b/backend-v2/runtime/BigInteger.c index 4a205ad6..6c6a8737 100644 --- a/backend-v2/runtime/BigInteger.c +++ b/backend-v2/runtime/BigInteger.c @@ -1,4 +1,5 @@ #include "BigInteger.h" +#include "Exceptions.h" #include "Object.h" #include "String.h" @@ -42,6 +43,25 @@ bool BigInteger_equals(BigInteger *self, BigInteger *other) { return cmp == 0; } +bool BigInteger_intEquals(int32_t other, BigInteger *self) { + int cmp = mpz_cmp_si(self->value, other); + Ptr_release(self); + return cmp == 0; +} + +bool BigInteger_equalsInt(BigInteger *self, int32_t other) { + int cmp = mpz_cmp_si(self->value, other); + Ptr_release(self); + return cmp == 0; +} + +bool BigInteger_equiv(BigInteger *self, BigInteger *other) { + bool retVal = BigInteger_equals(self, other); + Ptr_release(self); + Ptr_release(other); + return retVal; +} + /* outside refcount system */ uword_t BigInteger_hash(BigInteger *self) { return combineHash(5381, mpz_get_ui(self->value) + 5381); @@ -133,11 +153,11 @@ BigInteger *BigInteger_mul(BigInteger *self, BigInteger *other) { return retVal; } -void *BigInteger_div(BigInteger *self, BigInteger *other) { +RTValue BigInteger_div(BigInteger *self, BigInteger *other) { if (!mpz_cmp_si(other->value, 0)) { Ptr_release(self); Ptr_release(other); - return NULL; // Exception: divide by zero + throwArithmeticException_C("Divide by zero"); } if (mpz_divisible_p(self->value, other->value)) { @@ -159,7 +179,7 @@ void *BigInteger_div(BigInteger *self, BigInteger *other) { Ptr_release(self); Ptr_release(other); } - return retVal; + return RT_boxPtr(retVal); } else { Ratio *ratio = Ratio_createUnassigned(); mpq_set_num(ratio->value, self->value); @@ -167,7 +187,7 @@ void *BigInteger_div(BigInteger *self, BigInteger *other) { mpq_canonicalize(ratio->value); Ptr_release(self); Ptr_release(other); - return ratio; + return RT_boxPtr(ratio); } } @@ -178,9 +198,23 @@ bool BigInteger_gte(BigInteger *self, BigInteger *other) { return cmp >= 0; } +bool BigInteger_gt(BigInteger *self, BigInteger *other) { + int cmp = mpz_cmp(self->value, other->value); + Ptr_release(self); + Ptr_release(other); + return cmp > 0; +} + bool BigInteger_lt(BigInteger *self, BigInteger *other) { int cmp = mpz_cmp(self->value, other->value); Ptr_release(self); Ptr_release(other); return cmp < 0; } + +bool BigInteger_lte(BigInteger *self, BigInteger *other) { + int cmp = mpz_cmp(self->value, other->value); + Ptr_release(self); + Ptr_release(other); + return cmp <= 0; +} diff --git a/backend-v2/runtime/BigInteger.h b/backend-v2/runtime/BigInteger.h index b9229eae..988d42f8 100644 --- a/backend-v2/runtime/BigInteger.h +++ b/backend-v2/runtime/BigInteger.h @@ -1,9 +1,14 @@ #ifndef RT_BIG_INTEGER #define RT_BIG_INTEGER + +#ifdef __cplusplus +extern "C" { +#endif + #include "Double.h" +#include "RTValue.h" #include "String.h" #include -#include "RTValue.h" struct BigInteger { Object super; @@ -12,22 +17,32 @@ struct BigInteger { typedef struct BigInteger BigInteger; -BigInteger* BigInteger_createUninitialized(); -BigInteger* BigInteger_createUnassigned(); -BigInteger* BigInteger_createFromStr(const char * value); -BigInteger* BigInteger_createFromMpz(mpz_t value); -BigInteger* BigInteger_createFromInt(word_t value); +BigInteger *BigInteger_createUninitialized(); +BigInteger *BigInteger_createUnassigned(); +BigInteger *BigInteger_createFromStr(const char *value); +BigInteger *BigInteger_createFromMpz(mpz_t value); +BigInteger *BigInteger_createFromInt(word_t value); bool BigInteger_equals(BigInteger *self, BigInteger *other); +bool BigInteger_equiv(BigInteger *self, BigInteger *other); +bool BigInteger_intEquals(int32_t other, BigInteger *self); +bool BigInteger_equalsInt(BigInteger *self, int32_t other); + uword_t BigInteger_hash(BigInteger *self); -String *BigInteger_toString(BigInteger *self); +String *BigInteger_toString(BigInteger *self); void BigInteger_destroy(BigInteger *self); double BigInteger_toDouble(BigInteger *self); BigInteger *BigInteger_add(BigInteger *self, BigInteger *other); BigInteger *BigInteger_sub(BigInteger *self, BigInteger *other); BigInteger *BigInteger_mul(BigInteger *self, BigInteger *other); -void *BigInteger_div(BigInteger *self, BigInteger *other); +RTValue BigInteger_div(BigInteger *self, BigInteger *other); bool BigInteger_gte(BigInteger *self, BigInteger *other); +bool BigInteger_gt(BigInteger *self, BigInteger *other); bool BigInteger_lt(BigInteger *self, BigInteger *other); +bool BigInteger_lte(BigInteger *self, BigInteger *other); + +#ifdef __cplusplus +} +#endif #endif diff --git a/backend-v2/runtime/Boolean.h b/backend-v2/runtime/Boolean.h index 60bafc7f..861cb950 100644 --- a/backend-v2/runtime/Boolean.h +++ b/backend-v2/runtime/Boolean.h @@ -1,9 +1,18 @@ #ifndef RT_BOOLEAN #define RT_BOOLEAN + +#ifdef __cplusplus +extern "C" { +#endif + +#include "RTValue.h" #include "String.h" #include "defines.h" -#include "RTValue.h" -String *Boolean_toString(RTValue self); +String *Boolean_toString(RTValue self); + +#ifdef __cplusplus +} +#endif #endif diff --git a/backend-v2/runtime/CMakeLists.txt b/backend-v2/runtime/CMakeLists.txt index 60ab5372..d41309b4 100644 --- a/backend-v2/runtime/CMakeLists.txt +++ b/backend-v2/runtime/CMakeLists.txt @@ -6,11 +6,13 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() -set(CMAKE_CXX_FLAGS_DEBUG "-g -fsanitize=address -fno-omit-frame-pointer -Wall -Wextra -Wstrict-aliasing -Wno-unused-parameter") -set(CMAKE_CXX_FLAGS_RELEASE "-Ofast") +set(CMAKE_CXX_FLAGS_DEBUG "-g -fsanitize=address -fno-omit-frame-pointer -Wall -Wextra -Wstrict-aliasing -Wno-unused-parameter -fexceptions") +set(CMAKE_CXX_FLAGS_RELEASE "-Ofast -fexceptions") -set(CMAKE_C_FLAGS_DEBUG "-g -fsanitize=address -fno-omit-frame-pointer -Wall -Wextra -Wstrict-aliasing -Wno-unused-parameter") -set(CMAKE_C_FLAGS_RELEASE "-Ofast") +set(CMAKE_C_FLAGS_DEBUG "-g -fsanitize=address -fno-omit-frame-pointer -Wall -Wextra -Wstrict-aliasing -Wno-unused-parameter -fexceptions") +set(CMAKE_C_FLAGS_RELEASE "-Ofast -fexceptions") + +add_compile_definitions(BUILD_TYPE="${CMAKE_BUILD_TYPE}") set(SOURCES Transient.c @@ -50,6 +52,7 @@ include_directories(${CMOCKA_INCLUDE_DIRS}) set(COMMON_TEST_SOURCES tests/TestTools.c + tests/ExceptionsMock.c ) set(TEST_MODULES @@ -61,6 +64,7 @@ set(TEST_MODULES Symbol_test PersistentArrayMap_test BigInteger_test + Integer_test Ratio_test Boolean_test ) diff --git a/backend-v2/runtime/Class.c b/backend-v2/runtime/Class.c index 799bead9..ce48e48b 100644 --- a/backend-v2/runtime/Class.c +++ b/backend-v2/runtime/Class.c @@ -1,41 +1,21 @@ -#include "Object.h" #include "Class.h" #include "Hash.h" +#include "Object.h" #include "word.h" #include - Class *Class_createInterface(String *name, String *className, int32_t extendsInterfaceCount, - Class **extendsInterfaces, - int32_t staticMethodCount, - RTValue *staticMethodNames, - ClojureFunction **staticMethods, - int32_t methodCount, RTValue *methodNames) { - Class *retVal = Class_create( - name, className, extendsInterfaceCount, extendsInterfaces, 0, NULL, NULL, - staticMethodCount, staticMethodNames, staticMethods, 0, NULL, - methodCount, methodNames, NULL, 0, NULL); + Class **extendsInterfaces) { + Class *retVal = + Class_create(name, className, extendsInterfaceCount, extendsInterfaces); retVal->isInterface = true; return retVal; -} - -Class *Class_create(String *name, String *className, - int32_t superclassCount, Class **superclasses, - - int32_t staticFieldCount, RTValue *staticFieldNames, - RTValue *staticFields, - - int32_t staticMethodCount, RTValue *staticMethodNames, - ClojureFunction **staticMethods, +} - int32_t fieldCount, RTValue *fieldNames, - int32_t methodCount, RTValue *methodNames, - ClojureFunction **methods, +Class *Class_create(String *name, String *className, int32_t superclassCount, + Class **superclasses) { - int32_t implementedInterfacesCount, - ImplementedInterface **implementedInterfaces) { - Class *self = allocate(sizeof(Class)); self->isInterface = false; self->registerId = 0; // unregistered @@ -43,22 +23,9 @@ Class *Class_create(String *name, String *className, self->className = className; self->superclassCount = superclassCount; self->superclasses = superclasses; - - self->staticFieldCount = staticFieldCount; - self->staticFieldNames = staticFieldNames; - self->staticFields = staticFields; - - self->staticMethodCount = staticMethodCount; - self->staticMethodNames = staticMethodNames; - self->staticMethods = staticMethods; - - self->methodCount = methodCount; - self->methodNames = methodNames; - self->methods = methods; - - self->implementedInterfacesCount = implementedInterfacesCount; - self->implementedInterfaces = implementedInterfaces; - self->superclasses = superclasses; + + self->compilerExtension = NULL; + self->compilerExtensionDestructor = NULL; Object_create((Object *)self, classType); return self; } @@ -70,8 +37,10 @@ bool Class_equals(Class *self, Class *other) { } /* outside refcount system */ -int32_t Class_hash(Class *self) { // CONSIDER: Ignoring fields for now, is it wise? - return combineHash(avalanche(self->registerId), Object_hash((Object *)self->name)); +int32_t +Class_hash(Class *self) { // CONSIDER: Ignoring fields for now, is it wise? + return combineHash(avalanche(self->registerId), + Object_hash((Object *)self->name)); } String *Class_toString(Class *self) { @@ -87,133 +56,105 @@ void Class_destroy(Class *self) { for (int32_t i = 0; i < self->superclassCount; i++) Ptr_release(self->superclasses[i]); - if(self->superclasses) deallocate(self->superclasses); - for (int32_t i = 0; i < self->staticFieldCount; ++i) { - release(self->staticFieldNames[i]); - release(self->staticFields[i]); - } - if (self->staticFieldNames) deallocate(self->staticFieldNames); - if (self->staticFields) deallocate(self->staticFields); - - for (int32_t i = 0; i < self->staticMethodCount; ++i) { - release(self->staticMethodNames[i]); - Ptr_release(self->staticMethods[i]); - } - if (self->staticMethodNames) deallocate(self->staticMethodNames); - if (self->staticMethods) deallocate(self->staticMethods); - - for (int32_t i = 0; i < self->fieldCount; ++i) { - release(self->fieldNames[i]); - } - - if (self->fieldNames) deallocate(self->fieldNames); - - for (int32_t i = 0; i < self->methodCount; ++i) { - release(self->methodNames[i]); - // Will be NULL for interfaces - if(self->methods) Ptr_release(self->methods[i]); - } - if (self->methodNames) deallocate(self->methodNames); - if (self->methods) deallocate(self->methods); - - for (int32_t i = 0; i < self->implementedInterfacesCount; i++) { - for (int32_t j = 0; j < self->implementedInterfaces[i]->interface->methodCount; j++) { - Ptr_release(self->implementedInterfaces[i]->functions[j]); - } - Ptr_release(self->implementedInterfaces[i]->interface); - if (self->implementedInterfaces[i]->functions) - deallocate(self->implementedInterfaces[i]->functions); - deallocate(self->implementedInterfaces[i]); + if (self->superclasses) + deallocate(self->superclasses); + + if (self->compilerExtension && self->compilerExtensionDestructor) { + self->compilerExtensionDestructor(self->compilerExtension); } - if (self->implementedInterfaces) deallocate(self->implementedInterfaces); +} + +// Bridge declarations (implemented in C++ side) +__attribute__((weak)) int32_t ClassExtension_fieldIndex(void *ext, + RTValue field) { + return -1; +} +__attribute__((weak)) int32_t +ClassExtension_staticFieldIndex(void *ext, RTValue staticField) { + return -1; +} +__attribute__((weak)) RTValue ClassExtension_getIndexedStaticField(void *ext, + int32_t i) { + return RT_boxNil(); +} +__attribute__((weak)) RTValue +ClassExtension_setIndexedStaticField(void *ext, int32_t i, RTValue value) { + return value; +} +__attribute__((weak)) ClojureFunction * +ClassExtension_resolveInstanceCall(void *ext, RTValue name, int32_t argCount) { + return NULL; } int32_t Class_fieldIndex(Class *self, RTValue field) { int32_t retVal = -1; - for (int32_t i = 0; i < self->fieldCount; i++) { - RTValue fieldName = self->fieldNames[i]; - if (equals(fieldName, field)) { - retVal = i; - } - } + if (self->compilerExtension) { + retVal = ClassExtension_fieldIndex(self->compilerExtension, field); + } Ptr_release(self); - release(field); + release(field); return retVal; } int32_t Class_staticFieldIndex(Class *self, RTValue staticField) { int32_t retVal = -1; - for (int32_t i = 0; i < self->staticFieldCount; i++) { - RTValue fieldName = self->staticFieldNames[i]; - if (equals(fieldName, staticField)) { - retVal = i; - } - } + if (self->compilerExtension) { + retVal = + ClassExtension_staticFieldIndex(self->compilerExtension, staticField); + } Ptr_release(self); release(staticField); return retVal; } RTValue Class_setIndexedStaticField(Class *self, int32_t i, RTValue value) { - assert (i >= 0 && i < (int32_t)self->staticFieldCount && "Internal error - unsafe index"); - - RTValue oldValue = self->staticFields[i]; - self->staticFields[i] = value; - release(oldValue); + RTValue retVal = value; + if (self->compilerExtension) { + retVal = + ClassExtension_setIndexedStaticField(self->compilerExtension, i, value); + } Ptr_release(self); - return value; + return retVal; } RTValue Class_getIndexedStaticField(Class *self, int32_t i) { - assert (i >= 0 && i < (int32_t)self->staticFieldCount && "Internal error - unsafe index"); - RTValue retVal = self->staticFields[i]; - retain(retVal); + RTValue retVal = RT_boxNil(); + if (self->compilerExtension) { + retVal = ClassExtension_getIndexedStaticField(self->compilerExtension, i); + } Ptr_release(self); return retVal; } -ClojureFunction *Class_resolveInstanceCall(Class *self, RTValue name, int32_t argCount) { - // Class method +ClojureFunction *Class_resolveInstanceCall(Class *self, RTValue name, + int32_t argCount) { ClojureFunction *retVal = NULL; - for (int32_t i = 0; i < self->methodCount; i++) { - if (equals(name, self->methodNames[i]) - && Function_validCallWithArgCount(self->methods[i], argCount)) { - retVal = self->methods[i]; + + if (self->compilerExtension) { + retVal = ClassExtension_resolveInstanceCall(self->compilerExtension, name, + argCount); + if (retVal) { Ptr_retain(retVal); Ptr_release(self); release(name); return retVal; } } - - // Interface implementation - for (int32_t i = 0; i < self->implementedInterfacesCount; i++) { - for (int32_t j = 0; j < self->implementedInterfaces[i]->interface->methodCount; j++) { - if (equals(name, self->implementedInterfaces[i]->interface->methodNames[j]) - && Function_validCallWithArgCount(self->implementedInterfaces[i]->functions[j], argCount)) { - retVal = self->implementedInterfaces[i]->functions[j]; - Ptr_retain(retVal); - Ptr_release(self); - release(name); - return retVal; - } - } - } - // Resolve in superclasses + // Resolve in superclasses (still needed in C if extension doesn't handle + // hierarchy) for (int32_t i = 0; i < self->superclassCount; i++) { - Ptr_retain(self->superclasses[0]); + Ptr_retain(self->superclasses[i]); retVal = Class_resolveInstanceCall(self->superclasses[i], name, argCount); if (retVal) { Ptr_release(self); return retVal; - } - } - + } + } Ptr_release(self); release(name); - + /* NULL signals the function was not found. */ return retVal; } diff --git a/backend-v2/runtime/Class.h b/backend-v2/runtime/Class.h index 071b9bff..606979cf 100644 --- a/backend-v2/runtime/Class.h +++ b/backend-v2/runtime/Class.h @@ -1,14 +1,18 @@ #ifndef RT_CLASS #define RT_CLASS -#include -#include +#ifdef __cplusplus +extern "C" { +#endif + +#include "Function.h" +#include "defines.h" +#include "word.h" #include +#include #include +#include #include -#include "defines.h" -#include "Function.h" -#include "word.h" typedef struct Object Object; typedef struct Class Interface; @@ -29,58 +33,18 @@ typedef struct Class { int32_t superclassCount; struct Class **superclasses; - int32_t staticFieldCount; - /* Keywords */ - RTValue *staticFieldNames; - /* Anythings */ - RTValue *staticFields; - - int32_t staticMethodCount; - /* Keywords */ - RTValue *staticMethodNames; - ClojureFunction **staticMethods; - - int32_t fieldCount; - /* Keywords */ - RTValue *fieldNames; - - int32_t methodCount; - /* Keywords */ - RTValue *methodNames; - ClojureFunction **methods; - - // This does not contain interfaces of the superclass - int32_t implementedInterfacesCount; - ImplementedInterface **implementedInterfaces; + void *compilerExtension; + void (*compilerExtensionDestructor)(void *); } Class; // Class owns all its arguments Class *Class_create(String *name, String *className, int32_t superclassCount, - Class **superclasses, - - int32_t staticFieldCount, RTValue *staticFieldNames, - RTValue *staticFields, - - int32_t staticMethodCount, RTValue *staticMethodNames, - ClojureFunction **staticMethods, - - int32_t fieldCount, RTValue *fieldNames, - int32_t methodCount, RTValue *methodNames, - ClojureFunction **methods, - - int32_t implementedInterfacesCount, - ImplementedInterface **implementedInterfaces); - - + Class **superclasses); Class *Class_createInterface(String *name, String *className, int32_t extendsInterfaceCount, - Class **extendsInterfaces, - int32_t staticMethodCount, - RTValue *staticMethodNames, - ClojureFunction **staticMethods, - int32_t methodCount, RTValue *methodNames); + Class **extendsInterfaces); bool Class_equals(Class *self, Class *other); int32_t Class_hash(Class *self); @@ -91,6 +55,11 @@ int32_t Class_fieldIndex(Class *self, /* Keyword */ RTValue field); int32_t Class_staticFieldIndex(Class *self, /* Keyword */ RTValue staticField); RTValue Class_setIndexedStaticField(Class *self, int32_t i, RTValue value); RTValue Class_getIndexedStaticField(Class *self, int32_t i); -ClojureFunction *Class_resolveInstanceCall(Class *self, RTValue name, int32_t argCount); +ClojureFunction *Class_resolveInstanceCall(Class *self, RTValue name, + int32_t argCount); + +#ifdef __cplusplus +} +#endif #endif diff --git a/backend-v2/runtime/ConcurrentHashMap.c b/backend-v2/runtime/ConcurrentHashMap.c index 2ec92726..cd460c3d 100644 --- a/backend-v2/runtime/ConcurrentHashMap.c +++ b/backend-v2/runtime/ConcurrentHashMap.c @@ -7,6 +7,7 @@ #include "RTValue.h" #include "word.h" #include +#include "Exceptions.h" extern uword_t tryReservingEmptyNode(ConcurrentHashMapEntry *entry, RTValue key, RTValue value, uword_t keyHash); extern bool tryReplacingEntry(ConcurrentHashMapEntry *entry, RTValue key, RTValue value, uword_t keyHash, uword_t encounteredHash); @@ -167,11 +168,10 @@ void ConcurrentHashMap_assoc(ConcurrentHashMap *self, RTValue key, RTValue value lastChainEntryLeaps = atomic_load_explicit(&(lastChainEntry->leaps), memory_order_relaxed); } } - /* The loop failed - the table is overcrowded and needs a migration - TODO + releases */ - printf("keyHash: %lu, startindex: %lu endChainIndex: %lu lastIndex: %lu\n", keyHash, startIndex, lastChainEntryIndex, index); + /* The loop failed - the table is overcrowded and needs a migration */ release(key); release(value); - assert(false && "Overcrowded - resizing not yet supported"); + throwIllegalStateException_C("ConcurrentHashMap is overcrowded - resizing not yet supported"); } /* outside refcount system */ diff --git a/backend-v2/runtime/ConcurrentHashMap.h b/backend-v2/runtime/ConcurrentHashMap.h index 4b66281e..9cf5f8f0 100644 --- a/backend-v2/runtime/ConcurrentHashMap.h +++ b/backend-v2/runtime/ConcurrentHashMap.h @@ -19,6 +19,10 @@ using std::memory_order_seq_cst; #else #include #endif +#ifdef __cplusplus +extern "C" { +#endif + #include "RTValue.h" #include "String.h" #include "defines.h" @@ -62,4 +66,8 @@ uword_t ConcurrentHashMap_hash(ConcurrentHashMap *self); String *ConcurrentHashMap_toString(ConcurrentHashMap *self); void ConcurrentHashMap_destroy(ConcurrentHashMap *self); +#ifdef __cplusplus +} +#endif + #endif diff --git a/backend-v2/runtime/Double.h b/backend-v2/runtime/Double.h index 8fc87965..ec92462a 100644 --- a/backend-v2/runtime/Double.h +++ b/backend-v2/runtime/Double.h @@ -1,9 +1,18 @@ #ifndef RT_DOUBLE #define RT_DOUBLE + +#ifdef __cplusplus +extern "C" { +#endif + +#include "RTValue.h" #include "String.h" #include "defines.h" -#include "RTValue.h" -String *Double_toString(RTValue self); +String *Double_toString(RTValue self); + +#ifdef __cplusplus +} +#endif #endif diff --git a/backend-v2/runtime/Exceptions.h b/backend-v2/runtime/Exceptions.h new file mode 100644 index 00000000..caccba99 --- /dev/null +++ b/backend-v2/runtime/Exceptions.h @@ -0,0 +1,30 @@ +#ifndef RT_RUNTIME_EXCEPTIONS_H +#define RT_RUNTIME_EXCEPTIONS_H + +#include "RTValue.h" +#include "word.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Base throw function compatible with LanguageException +[[noreturn]] void throwLanguageException_C(const char *name, RTValue message, + RTValue payload); + +// Standard Clojure-like exceptions +[[noreturn]] void throwArityException_C(int expected, int actual); +[[noreturn]] void throwIllegalArgumentException_C(const char *message); +[[noreturn]] void throwIllegalStateException_C(const char *message); +[[noreturn]] void throwUnsupportedOperationException_C(const char *message); +[[noreturn]] void throwArithmeticException_C(const char *message); +[[noreturn]] void throwIndexOutOfBoundsException_C(uword_t index, + uword_t count); +[[noreturn]] void +throwInternalInconsistencyException_C(const char *errorMessage); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/backend-v2/runtime/Function.h b/backend-v2/runtime/Function.h index 04fe7f4b..eea0fc33 100644 --- a/backend-v2/runtime/Function.h +++ b/backend-v2/runtime/Function.h @@ -1,19 +1,24 @@ #ifndef RT_FUNCTION #define RT_FUNCTION + +#ifdef __cplusplus +extern "C" { +#endif + +#include "RTValue.h" #include "String.h" #include "defines.h" -#include "RTValue.h" #define INVOKATION_CACHE_SIZE 3 typedef struct InvokationCache InvokationCache; -struct FunctionMethod { +struct FunctionMethod { unsigned char index; uword_t fixedArity; uword_t isVariadic; void *baselineImplementation; - char *loopId; + char *loopId; uword_t closedOversCount; RTValue *closedOvers; }; @@ -34,15 +39,22 @@ struct ClojureFunction { typedef struct ClojureFunction ClojureFunction; -struct ClojureFunction* Function_create(uword_t methodCount, uword_t uniqueId, uword_t maxArity, bool once); +struct ClojureFunction *Function_create(uword_t methodCount, uword_t uniqueId, + uword_t maxArity, bool once); -void Function_fillMethod(struct ClojureFunction *self, uword_t position, uword_t index, uword_t fixedArity, bool isVariadic, char *loopId, word_t closedOversCount, ...); +void Function_fillMethod(struct ClojureFunction *self, uword_t position, + uword_t index, uword_t fixedArity, bool isVariadic, + char *loopId, word_t closedOversCount, ...); bool Function_validCallWithArgCount(ClojureFunction *self, uword_t argCount); bool Function_equals(ClojureFunction *self, ClojureFunction *other); uword_t Function_hash(ClojureFunction *self); -String *Function_toString(ClojureFunction *self); +String *Function_toString(ClojureFunction *self); void Function_destroy(ClojureFunction *self); void Function_cleanupOnce(ClojureFunction *self); +#ifdef __cplusplus +} +#endif + #endif diff --git a/backend-v2/runtime/Integer.c b/backend-v2/runtime/Integer.c index 2eb07ffb..1ddeb173 100644 --- a/backend-v2/runtime/Integer.c +++ b/backend-v2/runtime/Integer.c @@ -1,11 +1,11 @@ #include "Integer.h" -#include "Hash.h" +#include "Exceptions.h" +#include "word.h" #include "Object.h" #include "RTValue.h" #include "Ratio.h" #include "String.h" #include -#include /* mem done */ String *Integer_toString(RTValue self) { @@ -28,7 +28,9 @@ int64_t gcd(int64_t a, int64_t b) { // Integer or Ratio RTValue Integer_div(int32_t num, int32_t den) { - assert(den != 0 && "Internal error: Divide by zero."); + if (den == 0) { + throwArithmeticException_C("Divide by zero"); + } if (!num) return RT_boxInt32(0); int32_t g = gcd(num, den); diff --git a/backend-v2/runtime/Integer.h b/backend-v2/runtime/Integer.h index 6488729a..878ee698 100644 --- a/backend-v2/runtime/Integer.h +++ b/backend-v2/runtime/Integer.h @@ -1,11 +1,20 @@ #ifndef RT_INTEGER #define RT_INTEGER + +#ifdef __cplusplus +extern "C" { +#endif + #include "RTValue.h" #include "String.h" #include "defines.h" -String *Integer_toString(RTValue self); +String *Integer_toString(RTValue self); RTValue Integer_div(int32_t num, int32_t den); +#ifdef __cplusplus +} +#endif + #endif diff --git a/backend-v2/runtime/Keyword.h b/backend-v2/runtime/Keyword.h index 13371bec..d2b8956c 100644 --- a/backend-v2/runtime/Keyword.h +++ b/backend-v2/runtime/Keyword.h @@ -1,6 +1,10 @@ #ifndef RT_KEYWORD #define RT_KEYWORD +#ifdef __cplusplus +extern "C" { +#endif + #include "ObjectProto.h" #include "RTValue.h" #include "defines.h" @@ -16,4 +20,8 @@ RTValue Keyword_create(String *string); String *Keyword_toString(RTValue self); uint32_t Keyword_getInternCount(); +#ifdef __cplusplus +} +#endif + #endif diff --git a/backend-v2/runtime/Nil.h b/backend-v2/runtime/Nil.h index 9417d88e..f664165e 100644 --- a/backend-v2/runtime/Nil.h +++ b/backend-v2/runtime/Nil.h @@ -1,9 +1,18 @@ #ifndef RT_NIL #define RT_NIL + +#ifdef __cplusplus +extern "C" { +#endif + +#include "RTValue.h" #include "String.h" #include "defines.h" -#include "RTValue.h" -String *Nil_toString(); +String *Nil_toString(); + +#ifdef __cplusplus +} +#endif #endif diff --git a/backend-v2/runtime/Object.c b/backend-v2/runtime/Object.c index c5f1a758..d19d28d9 100644 --- a/backend-v2/runtime/Object.c +++ b/backend-v2/runtime/Object.c @@ -77,3 +77,4 @@ extern bool Ptr_release(void *self); extern uword_t Ptr_hash(void *self); extern bool Ptr_equals(void *self, void *other); extern bool Ptr_isReusable(void *self); +extern bool equals_managed(RTValue self, RTValue other); diff --git a/backend-v2/runtime/Object.h b/backend-v2/runtime/Object.h index 60401e17..51d1bc8e 100644 --- a/backend-v2/runtime/Object.h +++ b/backend-v2/runtime/Object.h @@ -56,12 +56,20 @@ typedef struct String String; #define MEMORY_BANK_SIZE_MAX 10 +#ifdef __cplusplus +extern "C" { +#endif + extern void logBacktrace(); void printReferenceCounts(); extern _Atomic(uword_t) allocationCount[256]; extern _Atomic(uword_t) objectCount[256]; +#ifdef __cplusplus +} +#endif + // bank 0 - 32 bytes 2^5 // bank 1 - 64 bytes // bank 2 - 128 bytes @@ -74,8 +82,16 @@ extern _Atomic(uword_t) objectCount[256]; extern _Thread_local void *memoryBank[8]; extern _Thread_local int memoryBankSize[8]; +#ifdef __cplusplus +extern "C" { +#endif + void initialise_memory(); +#ifdef __cplusplus +} +#endif + inline void *allocate(size_t size) { #ifndef USE_MEMORY_BANKS return malloc(size); @@ -315,6 +331,8 @@ inline uword_t Object_hash(Object *restrict self) { assert(false && "Internal error: hash computation for NaN tagged types " "should be computed earlier."); } + // To silence the compiler warning, this code should never be reached. + return 0; } /* Outside of refcount system */ @@ -389,6 +407,8 @@ inline bool Object_equals(Object *self, Object *other) { assert(false && "Internal error: hash computation for NaN tagged types " "should be computed earlier."); } + // To silence the compiler warning, this code should never be reached. + return false; } /* Outside of refcount system - should it be like this? It probably shouldnt */ @@ -442,24 +462,8 @@ inline String *Object_toString(Object *restrict self) { default: assert(false && "Internal error: Object_toString got an unsupported type"); } -} - -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); - - assert(RT_isPtr(self) && "Internal error: Not a pointer"); - return Object_toString((Object *)RT_unboxPtr(self)); + // To silence the compiler warning, this code should never be reached. + return NULL; } inline bool Object_release(Object *restrict self) { @@ -534,6 +538,33 @@ inline bool Ptr_equals(void *self, void *other) { return Object_equals((Object *)self, (Object *)other); } +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)) { + release(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)); +} + +inline bool equals_managed(RTValue self, RTValue other) { + bool result = equals(self, other); + release(self); + release(other); + return result; +} + #pragma clang diagnostic pop #endif diff --git a/backend-v2/runtime/PersistentArrayMap.c b/backend-v2/runtime/PersistentArrayMap.c index 4653c59d..4ee11c1f 100644 --- a/backend-v2/runtime/PersistentArrayMap.c +++ b/backend-v2/runtime/PersistentArrayMap.c @@ -4,6 +4,7 @@ #include "RTValue.h" #include "defines.h" #include +#include "Exceptions.h" static PersistentArrayMap *EMPTY = NULL; @@ -37,15 +38,19 @@ PersistentArrayMap *PersistentArrayMap_copy(PersistentArrayMap *other) { /* mem done */ PersistentArrayMap *PersistentArrayMap_createMany(int32_t pairCount, ...) { - assert(pairCount < HASHTABLE_THRESHOLD + 1 && - "Maps of size > 8 not supported yet"); + if (pairCount > HASHTABLE_THRESHOLD) { + char buf[64]; + snprintf(buf, sizeof(buf), "Maps of size > %d not supported yet", + HASHTABLE_THRESHOLD); + throwUnsupportedOperationException_C(buf); + } va_list args; va_start(args, pairCount); PersistentArrayMap *self = PersistentArrayMap_create(); self->count = pairCount; - for (uword_t i = 0; i < pairCount; i++) { + for (uword_t i = 0; i < (uword_t)pairCount; i++) { RTValue k = va_arg(args, RTValue); RTValue v = va_arg(args, RTValue); self->keys[i] = k; @@ -149,8 +154,15 @@ PersistentArrayMap *PersistentArrayMap_assoc(PersistentArrayMap *self, word_t found = PersistentArrayMap_indexOf(self, key); bool reusable = Ptr_isReusable(self); if (found == -1) { - assert(self->count < HASHTABLE_THRESHOLD && - "Maps of size > 8 not supported yet"); + if (self->count >= HASHTABLE_THRESHOLD) { + Ptr_release(self); + release(key); + release(value); + char buf[64]; + snprintf(buf, sizeof(buf), "Maps of size > %d not supported yet", + HASHTABLE_THRESHOLD); + throwUnsupportedOperationException_C(buf); + } PersistentArrayMap *retVal = reusable ? self : PersistentArrayMap_copy(self); retVal->keys[retVal->count] = key; @@ -243,7 +255,7 @@ RTValue PersistentArrayMap_dynamic_get(RTValue self, RTValue key) { if (getType(self) != persistentArrayMapType) { release(self); release(key); - return RT_boxNil(); + throwIllegalArgumentException_C("Argument must be a PersistentArrayMap"); } return PersistentArrayMap_get(RT_unboxPtr(self), key); } diff --git a/backend-v2/runtime/PersistentArrayMap.h b/backend-v2/runtime/PersistentArrayMap.h index c75ca8b7..86d6d198 100644 --- a/backend-v2/runtime/PersistentArrayMap.h +++ b/backend-v2/runtime/PersistentArrayMap.h @@ -1,8 +1,12 @@ #ifndef RT_PERSISTENT_ARRAY_MAP #define RT_PERSISTENT_ARRAY_MAP -#include "String.h" +#ifdef __cplusplus +extern "C" { +#endif + #include "RTValue.h" +#include "String.h" // http://blog.higher-order.net/2009/09/08/understanding-clojures-persistenthashmap-deftwice // https://groups.google.com/g/clojure/c/o12kFA-j1HA // https://gist.github.com/mrange/d6e7415113ebfa52ccb660f4ce534dd4 @@ -18,20 +22,28 @@ struct PersistentArrayMap { RTValue values[HASHTABLE_THRESHOLD]; }; -PersistentArrayMap* PersistentArrayMap_empty(); +PersistentArrayMap *PersistentArrayMap_empty(); -bool PersistentArrayMap_equals(PersistentArrayMap *self, PersistentArrayMap *other); +bool PersistentArrayMap_equals(PersistentArrayMap *self, + PersistentArrayMap *other); uword_t PersistentArrayMap_hash(PersistentArrayMap *self); String *PersistentArrayMap_toString(PersistentArrayMap *self); -void PersistentArrayMap_destroy(PersistentArrayMap *self, bool deallocateChildren); +void PersistentArrayMap_destroy(PersistentArrayMap *self, + bool deallocateChildren); -PersistentArrayMap* PersistentArrayMap_assoc(PersistentArrayMap *self, RTValue key, RTValue value); -PersistentArrayMap* PersistentArrayMap_dissoc(PersistentArrayMap *self, RTValue key); +PersistentArrayMap *PersistentArrayMap_assoc(PersistentArrayMap *self, + RTValue key, RTValue value); +PersistentArrayMap *PersistentArrayMap_dissoc(PersistentArrayMap *self, + RTValue key); RTValue PersistentArrayMap_get(PersistentArrayMap *self, RTValue key); RTValue PersistentArrayMap_dynamic_get(RTValue self, RTValue key); word_t PersistentArrayMap_indexOf(PersistentArrayMap *self, RTValue key); -PersistentArrayMap* PersistentArrayMap_create(); -PersistentArrayMap* PersistentArrayMap_createMany(int32_t pairCount, ...); +PersistentArrayMap *PersistentArrayMap_create(); +PersistentArrayMap *PersistentArrayMap_createMany(int32_t pairCount, ...); + +#ifdef __cplusplus +} +#endif #endif diff --git a/backend-v2/runtime/PersistentList.h b/backend-v2/runtime/PersistentList.h index c8a57966..a84e1b0e 100644 --- a/backend-v2/runtime/PersistentList.h +++ b/backend-v2/runtime/PersistentList.h @@ -1,8 +1,12 @@ #ifndef RT_PERSISTENT_LIST #define RT_PERSISTENT_LIST -#include "String.h" +#ifdef __cplusplus +extern "C" { +#endif + #include "RTValue.h" +#include "String.h" typedef struct Object Object; typedef struct PersistentList PersistentList; @@ -14,7 +18,7 @@ struct PersistentList { uint64_t count; }; -PersistentList* PersistentList_empty(); +PersistentList *PersistentList_empty(); bool PersistentList_equals(PersistentList *self, PersistentList *other); uint64_t PersistentList_hash(PersistentList *self); @@ -25,5 +29,8 @@ PersistentList *PersistentList_create(RTValue first, PersistentList *rest); PersistentList *PersistentList_conj(PersistentList *self, RTValue other); PersistentList *PersistentList_createMany(int32_t argCount, ...); +#ifdef __cplusplus +} +#endif #endif diff --git a/backend-v2/runtime/PersistentVector.c b/backend-v2/runtime/PersistentVector.c index 8dee9cd6..677df3da 100644 --- a/backend-v2/runtime/PersistentVector.c +++ b/backend-v2/runtime/PersistentVector.c @@ -4,6 +4,7 @@ #include "RTValue.h" #include "Transient.h" #include +#include "Exceptions.h" PersistentVector *EMPTY_VECTOR = NULL; @@ -57,6 +58,13 @@ void PersistentVector_initialise() { EMPTY_VECTOR->tail->count = 0; } +void PersistentVector_cleanup() { + if (EMPTY_VECTOR) { + Ptr_release(EMPTY_VECTOR); + EMPTY_VECTOR = NULL; + } +} + bool PersistentVector_equals(PersistentVector *restrict self, PersistentVector *restrict other) { if (self->count != other->count) @@ -105,10 +113,10 @@ PersistentVector * PersistentVector_assoc_internal(PersistentVector *restrict self, uword_t index, RTValue other) { if (index > self->count) { - // TODO - Should throw + uword_t cnt = self->count; Ptr_release(self); release(other); - return NULL; + throwIndexOutOfBoundsException_C(index, cnt); } if (index == self->count) return PersistentVector_conj_internal(self, other); @@ -325,10 +333,10 @@ PersistentVectorNode *PersistentVector_nthBlock(PersistentVector *self, /* mem done */ RTValue PersistentVector_nth(PersistentVector *restrict self, uword_t index) { - /* TODO - should throw */ if (index >= self->count) { + uword_t cnt = self->count; Ptr_release(self); - return RT_boxNil(); + throwIndexOutOfBoundsException_C(index, cnt); } uword_t tailOffset = self->count - self->tail->count; if (index >= tailOffset) { @@ -352,10 +360,11 @@ RTValue PersistentVector_nth(PersistentVector *restrict self, uword_t index) { /* mem done */ RTValue PersistentVector_dynamic_nth(PersistentVector *restrict self, RTValue indexObject) { - /* TODO: should throw */ - /* we should support big integers here as well */ - if (!RT_isInt32(indexObject)) - return RT_boxNil(); + if (!RT_isInt32(indexObject)) { + Ptr_release(self); + release(indexObject); + throwIllegalArgumentException_C("Index must be an integer"); + } uword_t index = RT_unboxInt32(indexObject); return PersistentVector_nth(self, index); } @@ -406,7 +415,7 @@ PersistentVector * PersistentVector_pop_internal(PersistentVector *restrict self) { if (!self->count) { Ptr_release(self); - return NULL; // TODO: Pop on empty vector throws exception + throwIllegalStateException_C("Can't pop empty vector"); } PersistentVector *new; diff --git a/backend-v2/runtime/PersistentVector.h b/backend-v2/runtime/PersistentVector.h index d154291d..9fb092f8 100644 --- a/backend-v2/runtime/PersistentVector.h +++ b/backend-v2/runtime/PersistentVector.h @@ -1,6 +1,10 @@ #ifndef RT_PERSISTENT_VECTOR #define RT_PERSISTENT_VECTOR +#ifdef __cplusplus +extern "C" { +#endif + #include "String.h" #define RRB_BITS 5 @@ -9,11 +13,15 @@ #define RRB_BRANCHING (1 << RRB_BITS) #define RRB_MASK (RRB_BRANCHING - 1) -/* - * The persistent vector's update performance is generally between 5-8x slower than an ArrayList. - * The transient vector's update performance is comparable to the ArrayList, usually less than 1.5 times slower. - * Iteration is about 3 times slower, and lookups take no more than 2 times more time - * (unless the vector contains less than 1000 elements, in which case it is about just as fast). +void PersistentVector_initialise(); +void PersistentVector_cleanup(); + +/* + * The persistent vector's update performance is generally between 5-8x slower + * than an ArrayList. The transient vector's update performance is comparable to + * the ArrayList, usually less than 1.5 times slower. Iteration is about 3 times + * slower, and lookups take no more than 2 times more time (unless the vector + * contains less than 1000 elements, in which case it is about just as fast). */ typedef struct PersistentVector PersistentVector; @@ -28,37 +36,55 @@ struct PersistentVector { PersistentVectorNode *root; }; -PersistentVector* PersistentVector_create(); -PersistentVector* PersistentVector_createMany(int32_t objCount, ...); +PersistentVector *PersistentVector_create(); +PersistentVector *PersistentVector_createMany(int32_t objCount, ...); -bool PersistentVector_equals(PersistentVector *restrict self, PersistentVector *restrict other); +bool PersistentVector_equals(PersistentVector *restrict self, + PersistentVector *restrict other); uword_t PersistentVector_hash(PersistentVector *restrict self); String *PersistentVector_toString(PersistentVector *restrict self); -void PersistentVector_destroy(PersistentVector *restrict self, bool deallocateChildren); - -PersistentVector* PersistentVector_conj_internal(PersistentVector *restrict self, RTValue other); -PersistentVector* PersistentVector_conj(PersistentVector *restrict self, RTValue other); -PersistentVector* PersistentVector_conj_BANG_(PersistentVector *restrict self, RTValue other); - -PersistentVector* PersistentVector_assoc_internal(PersistentVector *restrict self, uword_t index, RTValue other); -PersistentVector* PersistentVector_assoc(PersistentVector *restrict self, uword_t index, RTValue other); -PersistentVector* PersistentVector_assoc_BANG_(PersistentVector *restrict self, uword_t index, RTValue other); - -PersistentVector* PersistentVector_pop_internal(PersistentVector *restrict self); -PersistentVector* PersistentVector_pop(PersistentVector *restrict self); -PersistentVector* PersistentVector_pop_BANG_(PersistentVector *restrict self); - -RTValue PersistentVector_dynamic_nth(PersistentVector *restrict self, RTValue indexObject); +void PersistentVector_destroy(PersistentVector *restrict self, + bool deallocateChildren); + +PersistentVector * +PersistentVector_conj_internal(PersistentVector *restrict self, RTValue other); +PersistentVector *PersistentVector_conj(PersistentVector *restrict self, + RTValue other); +PersistentVector *PersistentVector_conj_BANG_(PersistentVector *restrict self, + RTValue other); + +PersistentVector * +PersistentVector_assoc_internal(PersistentVector *restrict self, uword_t index, + RTValue other); +PersistentVector *PersistentVector_assoc(PersistentVector *restrict self, + uword_t index, RTValue other); +PersistentVector *PersistentVector_assoc_BANG_(PersistentVector *restrict self, + uword_t index, RTValue other); + +PersistentVector * +PersistentVector_pop_internal(PersistentVector *restrict self); +PersistentVector *PersistentVector_pop(PersistentVector *restrict self); +PersistentVector *PersistentVector_pop_BANG_(PersistentVector *restrict self); + +RTValue PersistentVector_dynamic_nth(PersistentVector *restrict self, + RTValue indexObject); RTValue PersistentVector_nth(PersistentVector *restrict self, uword_t index); -PersistentVectorNode* PersistentVector_nthBlock(PersistentVector *restrict self, uword_t index); +PersistentVectorNode *PersistentVector_nthBlock(PersistentVector *restrict self, + uword_t index); void PersistentVector_print(PersistentVector *restrict self); -PersistentVector* PersistentVector_copy_root(PersistentVector *restrict self, uword_t transientID); -PersistentVector* PersistentVector_transient(PersistentVector *restrict self); -PersistentVector* PersistentVector_persistent_BANG_(PersistentVector *restrict self); +PersistentVector *PersistentVector_copy_root(PersistentVector *restrict self, + uword_t transientID); +PersistentVector *PersistentVector_transient(PersistentVector *restrict self); +PersistentVector * +PersistentVector_persistent_BANG_(PersistentVector *restrict self); uword_t PersistentVector_count(PersistentVector *restrict self); bool PersistentVector_contains(PersistentVector *restrict self, RTValue other); +#ifdef __cplusplus +} +#endif + #endif diff --git a/backend-v2/runtime/PersistentVectorIterator.h b/backend-v2/runtime/PersistentVectorIterator.h index 29b9b9ca..52ed68ad 100644 --- a/backend-v2/runtime/PersistentVectorIterator.h +++ b/backend-v2/runtime/PersistentVectorIterator.h @@ -1,10 +1,15 @@ #ifndef RT_PERSISTENT_VECTOR_ITERATOR #define RT_PERSISTENT_VECTOR_ITERATOR + +#ifdef __cplusplus +extern "C" { +#endif + #include "ObjectProto.h" /* #include "PersistentVectorNode.h" */ /* #include "PersistentVector.h" */ -#include #include "RTValue.h" +#include typedef struct PersistentVectorNode PersistentVectorNode; typedef struct PersistentVector PersistentVector; @@ -22,4 +27,8 @@ PersistentVectorIterator PersistentVector_iterator(PersistentVector *self); RTValue PersistentVector_iteratorGet(PersistentVectorIterator *it); RTValue PersistentVector_iteratorNext(PersistentVectorIterator *it); +#ifdef __cplusplus +} +#endif + #endif diff --git a/backend-v2/runtime/PersistentVectorNode.h b/backend-v2/runtime/PersistentVectorNode.h index 094440f7..e2c377f2 100644 --- a/backend-v2/runtime/PersistentVectorNode.h +++ b/backend-v2/runtime/PersistentVectorNode.h @@ -1,8 +1,13 @@ #ifndef RT_PERSISTENT_VECTOR_NODE #define RT_PERSISTENT_VECTOR_NODE + +#ifdef __cplusplus +extern "C" { +#endif + #include "RTValue.h" -typedef enum {leafNode, internalNode} NodeType; +typedef enum { leafNode, internalNode } NodeType; typedef struct PersistentVectorNode PersistentVectorNode; typedef struct String String; @@ -15,15 +20,34 @@ struct PersistentVectorNode { RTValue array[]; }; -PersistentVectorNode* PersistentVectorNode_allocate(uword_t count, NodeType type, uword_t transientID); -bool PersistentVectorNode_equals(PersistentVectorNode *restrict self, PersistentVectorNode *restrict other); +PersistentVectorNode *PersistentVectorNode_allocate(uword_t count, + NodeType type, + uword_t transientID); +bool PersistentVectorNode_equals(PersistentVectorNode *restrict self, + PersistentVectorNode *restrict other); uword_t PersistentVectorNode_hash(PersistentVectorNode *restrict self); String *PersistentVectorNode_toString(PersistentVectorNode *restrict self); -void PersistentVectorNode_destroy(PersistentVectorNode *restrict self, bool deallocateChildren); +void PersistentVectorNode_destroy(PersistentVectorNode *restrict self, + bool deallocateChildren); -PersistentVectorNode *PersistentVectorNode_pushTail(PersistentVectorNode *restrict parent, PersistentVectorNode *restrict self, PersistentVectorNode *restrict tailToPush, int32_t level, bool *copied, bool allowsReuse, uword_t vectorTransientID); -PersistentVectorNode *PersistentVectorNode_replacePath(PersistentVectorNode *restrict self, uword_t level, uword_t index, RTValue other, bool allowsReuse, uword_t vectorTransientID); -PersistentVectorNode *PersistentVectorNode_popTail(PersistentVectorNode *restrict self, PersistentVectorNode **restrict poppedLeaf, bool allowsReuse, uword_t vectorTransientID); +PersistentVectorNode *PersistentVectorNode_pushTail( + PersistentVectorNode *restrict parent, PersistentVectorNode *restrict self, + PersistentVectorNode *restrict tailToPush, int32_t level, bool *copied, + bool allowsReuse, uword_t vectorTransientID); +PersistentVectorNode * +PersistentVectorNode_replacePath(PersistentVectorNode *restrict self, + uword_t level, uword_t index, RTValue other, + bool allowsReuse, uword_t vectorTransientID); +PersistentVectorNode * +PersistentVectorNode_popTail(PersistentVectorNode *restrict self, + PersistentVectorNode **restrict poppedLeaf, + bool allowsReuse, uword_t vectorTransientID); + +bool PersistentVectorNode_contains(PersistentVectorNode *restrict self, + RTValue other); + +#ifdef __cplusplus +} +#endif -bool PersistentVectorNode_contains(PersistentVectorNode *restrict self, RTValue other); #endif diff --git a/backend-v2/runtime/Ratio.c b/backend-v2/runtime/Ratio.c index 6d265990..651dccce 100644 --- a/backend-v2/runtime/Ratio.c +++ b/backend-v2/runtime/Ratio.c @@ -1,4 +1,5 @@ #include "Ratio.h" +#include "Exceptions.h" #include "Object.h" #include "String.h" @@ -33,7 +34,7 @@ Ratio *Ratio_createFromMpq(mpq_t value) { /* mem done */ Ratio *Ratio_createFromInts(word_t numerator, word_t denominator) { if (!denominator) - return NULL; // Exception: divide by zero + throwArithmeticException_C("Divide by zero"); Ratio *self = Ratio_createUnassigned(); mpq_set_si(self->value, numerator, denominator); mpq_canonicalize(self->value); @@ -62,15 +63,15 @@ bool mpq_isInteger(Ratio *ratio) { } /* mem done */ -void *Ratio_simplify(Ratio *self) { +RTValue Ratio_simplify(Ratio *self) { // If Ratio is integer, create BigInteger from Ratio, otherwise return self if (mpq_isInteger(self)) { BigInteger *num = BigInteger_createUnassigned(); mpq_get_num(num->value, self->value); Ptr_release(self); - return num; + return RT_boxPtr(num); } else { - return self; + return RT_boxPtr(self); } } @@ -105,7 +106,7 @@ double Ratio_toDouble(Ratio *self) { return retVal; } -void *Ratio_add(Ratio *self, Ratio *other) { +RTValue Ratio_add(Ratio *self, Ratio *other) { bool selfReusable = Ptr_isReusable(self); bool otherReusable = Ptr_isReusable(other); Ratio *retVal; @@ -127,7 +128,7 @@ void *Ratio_add(Ratio *self, Ratio *other) { return Ratio_simplify(retVal); } -void *Ratio_sub(Ratio *self, Ratio *other) { +RTValue Ratio_sub(Ratio *self, Ratio *other) { bool selfReusable = Ptr_isReusable(self); bool otherReusable = Ptr_isReusable(other); Ratio *retVal; @@ -149,7 +150,7 @@ void *Ratio_sub(Ratio *self, Ratio *other) { return Ratio_simplify(retVal); } -void *Ratio_mul(Ratio *self, Ratio *other) { +RTValue Ratio_mul(Ratio *self, Ratio *other) { bool selfReusable = Ptr_isReusable(self); bool otherReusable = Ptr_isReusable(other); Ratio *retVal; @@ -171,11 +172,11 @@ void *Ratio_mul(Ratio *self, Ratio *other) { return Ratio_simplify(retVal); } -void *Ratio_div(Ratio *self, Ratio *other) { +RTValue Ratio_div(Ratio *self, Ratio *other) { if (!mpq_cmp_si(other->value, 0, 1)) { Ptr_release(self); Ptr_release(other); - return NULL; // Exception: divide by zero + throwArithmeticException_C("Divide by zero"); } bool selfReusable = Ptr_isReusable(self); bool otherReusable = Ptr_isReusable(other); @@ -205,9 +206,23 @@ bool Ratio_gte(Ratio *self, Ratio *other) { return cmp >= 0; } +bool Ratio_gt(Ratio *self, Ratio *other) { + int cmp = mpq_cmp(self->value, other->value); + Ptr_release(self); + Ptr_release(other); + return cmp > 0; +} + bool Ratio_lt(Ratio *self, Ratio *other) { int cmp = mpq_cmp(self->value, other->value); Ptr_release(self); Ptr_release(other); return cmp < 0; } + +bool Ratio_lte(Ratio *self, Ratio *other) { + int cmp = mpq_cmp(self->value, other->value); + Ptr_release(self); + Ptr_release(other); + return cmp <= 0; +} diff --git a/backend-v2/runtime/Ratio.h b/backend-v2/runtime/Ratio.h index 39143f41..69ae08ff 100644 --- a/backend-v2/runtime/Ratio.h +++ b/backend-v2/runtime/Ratio.h @@ -1,10 +1,15 @@ #ifndef RT_RATIO #define RT_RATIO + +#ifdef __cplusplus +extern "C" { +#endif + +#include "BigInteger.h" #include "Double.h" +#include "RTValue.h" #include "String.h" -#include "BigInteger.h" #include -#include "RTValue.h" typedef struct Object Object; @@ -15,25 +20,31 @@ struct Ratio { typedef struct Ratio Ratio; -Ratio* Ratio_createUninitialized(); -Ratio* Ratio_createUnassigned(); -Ratio* Ratio_createFromStr(const char * value); -Ratio* Ratio_createFromMpq(mpq_t value); -Ratio* Ratio_createFromInts(word_t numerator, word_t denominator); -Ratio* Ratio_createFromInt(word_t value); -Ratio* Ratio_createFromBigInteger(BigInteger * value); -void* Ratio_simplify(Ratio * value); +Ratio *Ratio_createUninitialized(); +Ratio *Ratio_createUnassigned(); +Ratio *Ratio_createFromStr(const char *value); +Ratio *Ratio_createFromMpq(mpq_t value); +Ratio *Ratio_createFromInts(word_t numerator, word_t denominator); +Ratio *Ratio_createFromInt(word_t value); +Ratio *Ratio_createFromBigInteger(BigInteger *value); +RTValue Ratio_simplify(Ratio *value); bool Ratio_equals(Ratio *self, Ratio *other); uword_t Ratio_hash(Ratio *self); -String *Ratio_toString(Ratio *self); +String *Ratio_toString(Ratio *self); void Ratio_destroy(Ratio *self); double Ratio_toDouble(Ratio *self); -void *Ratio_add(Ratio *self, Ratio *other); -void *Ratio_sub(Ratio *self, Ratio *other); -void *Ratio_mul(Ratio *self, Ratio *other); -void *Ratio_div(Ratio *self, Ratio *other); +RTValue Ratio_add(Ratio *self, Ratio *other); +RTValue Ratio_sub(Ratio *self, Ratio *other); +RTValue Ratio_mul(Ratio *self, Ratio *other); +RTValue Ratio_div(Ratio *self, Ratio *other); bool Ratio_gte(Ratio *self, Ratio *other); +bool Ratio_gt(Ratio *self, Ratio *other); bool Ratio_lt(Ratio *self, Ratio *other); +bool Ratio_lte(Ratio *self, Ratio *other); + +#ifdef __cplusplus +} +#endif #endif diff --git a/backend-v2/runtime/RuntimeInterface.c b/backend-v2/runtime/RuntimeInterface.c index 2135c9d6..b65e11c3 100644 --- a/backend-v2/runtime/RuntimeInterface.c +++ b/backend-v2/runtime/RuntimeInterface.c @@ -1,5 +1,8 @@ #include "RuntimeInterface.h" #include "ConcurrentHashMap.h" +#include "Keyword.h" +#include "PersistentVector.h" +#include "Symbol.h" #include #include @@ -49,6 +52,7 @@ void RuntimeInterface_cleanup() { Ptr_release(vars); vars = NULL; } + PersistentVector_cleanup(); } void printReferenceCounts() { @@ -59,6 +63,14 @@ void printReferenceCounts() { printf("\n"); } +void captureMemoryState(MemoryState *state) { + for (int i = 0; i < 256; i++) { + state->counts[i] = allocationCount[i]; + } + state->internedKeywords = Keyword_getInternCount(); + state->internedSymbols = Symbol_getInternCount(); +} + void logBacktrace() { void *array[1000]; char **strings; diff --git a/backend-v2/runtime/RuntimeInterface.h b/backend-v2/runtime/RuntimeInterface.h index d6bf03a2..b1a366a1 100644 --- a/backend-v2/runtime/RuntimeInterface.h +++ b/backend-v2/runtime/RuntimeInterface.h @@ -1,11 +1,30 @@ #ifndef RT_RUNTIME_INTERFACE #define RT_RUNTIME_INTERFACE + +#include + +#ifdef __cplusplus +extern "C" { +#endif + #include "Object.h" #include "RTValue.h" void RuntimeInterface_initialise(); void RuntimeInterface_cleanup(); +typedef struct { + uword_t counts[256]; + uint32_t internedKeywords; + uint32_t internedSymbols; +} MemoryState; + +void captureMemoryState(MemoryState *state); + +#ifdef __cplusplus +} +#endif + inline bool logicalValue(RTValue self) { if (RT_isNil(self)) return false; @@ -14,6 +33,14 @@ inline bool logicalValue(RTValue self) { return true; } +#ifdef __cplusplus +extern "C" { +#endif +void logBacktrace(); +#ifdef __cplusplus +} +#endif + inline void logException(const char *description) { printf("%s\n", description); logBacktrace(); @@ -42,9 +69,17 @@ inline bool unboxedEqualsBoolean(RTValue left, bool right) { return RT_unboxBool(left) == right; } +#ifdef __cplusplus +extern "C" { +#endif + void printReferenceCounts(); void **packPointerArgs(uword_t count, ...); RTValue *packValueArgs(uword_t count, ...); +#ifdef __cplusplus +} +#endif + #endif diff --git a/backend-v2/runtime/String.c b/backend-v2/runtime/String.c index 291579a1..8d252abc 100644 --- a/backend-v2/runtime/String.c +++ b/backend-v2/runtime/String.c @@ -4,6 +4,7 @@ #include "PersistentVector.h" #include "RTValue.h" #include +#include "Exceptions.h" /* outside refcount system */ uword_t String_computeHash(const char *str) { @@ -317,7 +318,13 @@ String *String_replace(String *self, String *target, String *replacement) { // TODO: Only the most basic version implemented: target and replacement are // both one-character strings - assert(target->count == 1 && replacement->count == 1); + if (target->count != 1 || replacement->count != 1) { + Ptr_release(self); + Ptr_release(target); + Ptr_release(replacement); + throwUnsupportedOperationException_C( + "replace is currently only supported for single-character strings"); + } String *compactified = String_compactify(self); String *retVal = String_createDynamic(compactified->count); @@ -341,3 +348,46 @@ String *String_replace(String *self, String *target, String *replacement) { } return retVal; } + +char String_charAt(String *self, uword_t index) { + if (index >= self->count) { + uword_t cnt = self->count; + Ptr_release(self); + throwIndexOutOfBoundsException_C(index, cnt); + } + + StringIterator it = String_iterator(self); + for (uword_t i = 0; i < index; i++) { + String_iteratorNext(&it); + } + char result = *String_iteratorGet(&it); + Ptr_release(self); + return result; +} + +String *String_subs(String *self, uword_t start, uword_t end) { + if (start > end || end > self->count) { + uword_t cnt = self->count; + Ptr_release(self); + throwIndexOutOfBoundsException_C(end, cnt); + } + + uword_t len = end - start; + String *res = String_createDynamic(len); + char *dest = getDyn(res); + + StringIterator it = String_iterator(self); + for (uword_t i = 0; i < start; i++) { + String_iteratorNext(&it); + } + + for (uword_t i = 0; i < len; i++) { + dest[i] = *String_iteratorGet(&it); + String_iteratorNext(&it); + } + dest[len] = '\0'; + res->hash = String_computeHash(dest); + + Ptr_release(self); + return res; +} diff --git a/backend-v2/runtime/String.h b/backend-v2/runtime/String.h index 8188e3b4..a4d23f52 100644 --- a/backend-v2/runtime/String.h +++ b/backend-v2/runtime/String.h @@ -1,6 +1,10 @@ #ifndef RT_STRING #define RT_STRING +#ifdef __cplusplus +extern "C" { +#endif + #include "PersistentVectorIterator.h" #include "RTValue.h" #include "defines.h" @@ -65,5 +69,11 @@ word_t String_indexOf(String *self, String *other); word_t String_indexOfFrom(String *self, String *other, word_t fromIndex); String *String_replace(String *self, String *target, String *replacement); +char String_charAt(String *self, uword_t index); +String *String_subs(String *self, uword_t start, uword_t end); + +#ifdef __cplusplus +} +#endif #endif diff --git a/backend-v2/runtime/Symbol.h b/backend-v2/runtime/Symbol.h index c837898d..8586a2cf 100644 --- a/backend-v2/runtime/Symbol.h +++ b/backend-v2/runtime/Symbol.h @@ -1,6 +1,10 @@ #ifndef RT_SYMBOL #define RT_SYMBOL +#ifdef __cplusplus +extern "C" { +#endif + #include "RTValue.h" #include "defines.h" #include @@ -15,4 +19,8 @@ RTValue Symbol_create(String *string); String *Symbol_toString(RTValue self); uint32_t Symbol_getInternCount(); +#ifdef __cplusplus +} +#endif + #endif diff --git a/backend-v2/runtime/tests/BigInteger_test.c b/backend-v2/runtime/tests/BigInteger_test.c index eecd1e11..9b6be782 100644 --- a/backend-v2/runtime/tests/BigInteger_test.c +++ b/backend-v2/runtime/tests/BigInteger_test.c @@ -61,9 +61,9 @@ static void test_bigint_arithmetic(void **state) { // Test division (exact) BigInteger *d1 = BigInteger_createFromInt(100); BigInteger *d2 = BigInteger_createFromInt(10); - void *res = BigInteger_div(d1, d2); + RTValue res = BigInteger_div(d1, d2); assert_non_null(res); - BigInteger *d3 = (BigInteger *)res; + BigInteger *d3 = RT_unboxPtr(res); Ptr_retain(d3); assert_double_equal(10.0, BigInteger_toDouble(d3), 0.0001); Ptr_release(d3); @@ -95,11 +95,21 @@ static void test_bigint_comparison(void **state) { }); } +static void test_bigint_division_by_zero(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + BigInteger *a = BigInteger_createFromInt(10); + BigInteger *b = BigInteger_createFromInt(0); + ASSERT_THROWS("ArithmeticException", { BigInteger_div(a, b); }); + }); +} + int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_bigint_creation), cmocka_unit_test(test_bigint_arithmetic), cmocka_unit_test(test_bigint_comparison), + cmocka_unit_test(test_bigint_division_by_zero), }; initialise_memory(); return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/backend-v2/runtime/tests/ConcurrentHashMap_test.c b/backend-v2/runtime/tests/ConcurrentHashMap_test.c index 4ab628d8..c2f6a6f5 100644 --- a/backend-v2/runtime/tests/ConcurrentHashMap_test.c +++ b/backend-v2/runtime/tests/ConcurrentHashMap_test.c @@ -94,6 +94,27 @@ static void concurrentMapThreadingAndPerformance(void **state) { pd(); } +static void test_concurrent_hash_map_overcrowded(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + // Create a very small map to ensure it gets overcrowded quickly + // initialSizeExponent = 2 -> size 4 + ConcurrentHashMap *m = ConcurrentHashMap_create(2); + + // Filling it should eventually trigger the overcrowded exception + // resizingThreshold = MIN(round(pow(4, 0.33)), 128) = MIN(1.58, 128) = 1 (approx) + // It's very small, so a few assocs should trigger it. + + ASSERT_THROWS("IllegalStateException", { + for (int i = 0; i < 100; i++) { + ConcurrentHashMap_assoc(m, RT_boxInt32(i), RT_boxInt32(i)); + } + }); + + Ptr_release(m); + }); +} + int main(void) { const struct CMUnitTest tests[] = { // There are problems with the map - bad performance all around and @@ -102,6 +123,7 @@ int main(void) { // Needs to be re-implemented? or at least checked with emini cmocka_unit_test_prestate(testScalingBehavior, concurrentMapThreadingAndPerformance), + cmocka_unit_test(test_concurrent_hash_map_overcrowded), }; initialise_memory(); srand(0); diff --git a/backend-v2/runtime/tests/ExceptionsMock.c b/backend-v2/runtime/tests/ExceptionsMock.c new file mode 100644 index 00000000..665656b5 --- /dev/null +++ b/backend-v2/runtime/tests/ExceptionsMock.c @@ -0,0 +1,67 @@ +#include "TestTools.h" + +jmp_buf exception_env; +char *last_exception_name = NULL; +bool exception_catchable = false; + +void reset_exception_state() { + last_exception_name = NULL; + exception_catchable = false; +} + +static void handle_exception(const char *name) { + last_exception_name = (char *)name; + if (exception_catchable) { + longjmp(exception_env, 1); + } +} + +void throwLanguageException_C(const char *name, RTValue message, + RTValue payload) { + handle_exception(name); + fprintf(stderr, "RT Exception Thrown: %s\n", name); + abort(); +} + +void throwArityException_C(int expected, int actual) { + handle_exception("ArityException"); + fprintf(stderr, "ArityException: expected %d, got %d\n", expected, actual); + abort(); +} + +void throwIllegalArgumentException_C(const char *message) { + handle_exception("IllegalArgumentException"); + fprintf(stderr, "IllegalArgumentException: %s\n", message); + abort(); +} + +void throwIllegalStateException_C(const char *message) { + handle_exception("IllegalStateException"); + fprintf(stderr, "IllegalStateException: %s\n", message); + abort(); +} + +void throwUnsupportedOperationException_C(const char *message) { + handle_exception("UnsupportedOperationException"); + fprintf(stderr, "UnsupportedOperationException: %s\n", message); + abort(); +} + +void throwArithmeticException_C(const char *message) { + handle_exception("ArithmeticException"); + fprintf(stderr, "ArithmeticException: %s\n", message); + abort(); +} + +void throwIndexOutOfBoundsException_C(uword_t index, uword_t count) { + handle_exception("IndexOutOfBoundsException"); + fprintf(stderr, "IndexOutOfBoundsException: index %lu, count %lu\n", index, + count); + abort(); +} + +void throwInternalInconsistencyException_C(const char *errorMessage) { + handle_exception("InternalInconsistencyException"); + fprintf(stderr, "InternalInconsistencyException: %s\n", errorMessage); + abort(); +} diff --git a/backend-v2/runtime/tests/Integer_test.c b/backend-v2/runtime/tests/Integer_test.c new file mode 100644 index 00000000..84206b10 --- /dev/null +++ b/backend-v2/runtime/tests/Integer_test.c @@ -0,0 +1,36 @@ +#include "TestTools.h" +#include +#include + +static void test_integer_div_basic(void **state) { + ASSERT_MEMORY_ALL_BALANCED({ + RTValue res = Integer_div(10, 2); + assert_true(RT_isInt32(res)); + assert_int_equal(RT_unboxInt32(res), 5); + + res = Integer_div(10, 3); + assert_true(getType(res) == ratioType); + Ptr_release(RT_unboxPtr(res)); + + res = Integer_div(0, 5); + assert_true(RT_isInt32(res)); + assert_int_equal(RT_unboxInt32(res), 0); + }); +} + +static void test_integer_division_by_zero(void **state) { + ASSERT_MEMORY_ALL_BALANCED({ + ASSERT_THROWS("ArithmeticException", { + Integer_div(10, 0); + }); + }); +} + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_integer_div_basic), + cmocka_unit_test(test_integer_division_by_zero), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/backend-v2/runtime/tests/PersistentArrayMap_test.c b/backend-v2/runtime/tests/PersistentArrayMap_test.c index 8309c93a..21a8da3a 100644 --- a/backend-v2/runtime/tests/PersistentArrayMap_test.c +++ b/backend-v2/runtime/tests/PersistentArrayMap_test.c @@ -229,6 +229,32 @@ static void test_get_ownership_race(void **state) { }); } +static void test_array_map_exceptions(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + // 1. dynamic_get with non-map + ASSERT_THROWS("IllegalArgumentException", { + PersistentArrayMap_dynamic_get(RT_boxInt32(1), RT_boxInt32(2)); + }); + + // 2. createMany with too many pairs + // Using a large number to exceed HASHTABLE_THRESHOLD (128) + ASSERT_THROWS("UnsupportedOperationException", { + PersistentArrayMap_createMany(200, RT_boxInt32(1), RT_boxInt32(1)); + }); + + // 3. assoc exceeding threshold + PersistentArrayMap *m = PersistentArrayMap_empty(); + // Fill up to threshold + for (int i = 0; i < HASHTABLE_THRESHOLD; i++) { + m = PersistentArrayMap_assoc(m, RT_boxInt32(i), RT_boxInt32(i)); + } + ASSERT_THROWS("UnsupportedOperationException", { + PersistentArrayMap_assoc(m, RT_boxInt32(HASHTABLE_THRESHOLD), RT_boxInt32(0)); + }); + }); +} + int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_basic_operations), @@ -240,6 +266,7 @@ int main(void) { cmocka_unit_test(test_to_string), cmocka_unit_test(test_duplicate_keys), cmocka_unit_test(test_get_ownership_race), + cmocka_unit_test(test_array_map_exceptions), }; initialise_memory(); PersistentArrayMap *warmup = PersistentArrayMap_empty(); diff --git a/backend-v2/runtime/tests/PersistentVector_test.c b/backend-v2/runtime/tests/PersistentVector_test.c index 435b3624..008189b1 100644 --- a/backend-v2/runtime/tests/PersistentVector_test.c +++ b/backend-v2/runtime/tests/PersistentVector_test.c @@ -281,12 +281,12 @@ static void vectorNthOutOfBoundsTest(void **state) { PersistentVector *v1 = PersistentVector_create(); // Bounds check on empty vector - Ptr_retain(v1); // nth consumes - RTValue out1 = PersistentVector_nth(v1, 0); - assert_true(RT_isNil(out1)); + ASSERT_THROWS("IndexOutOfBoundsException", { + PersistentVector_nth(v1, 0); // consumes v1 + }); PersistentVector *v2 = - PersistentVector_conj(v1, RT_boxInt32(99)); // v1 consumed + PersistentVector_createMany(1, RT_boxInt32(99)); // Valid access Ptr_retain(v2); // nth consumes @@ -294,11 +294,31 @@ static void vectorNthOutOfBoundsTest(void **state) { assert_int_equal(RT_unboxInt32(valid), 99); // Bounds check on size 1 vector (index 1 is out of bounds) - // Here we let nth consume v2 for the last time - RTValue out2 = PersistentVector_nth(v2, 1); - assert_true(RT_isNil(out2)); + ASSERT_THROWS("IndexOutOfBoundsException", { + PersistentVector_nth(v2, 1); // consumes v2 + }); + }); +} - // Ptr_release(v2) is no longer needed since the second nth consumed it +static void test_vector_bounds_exceptions(void **state) { + ASSERT_MEMORY_ALL_BALANCED({ + // 1. assoc out of bounds + PersistentVector *v1 = PersistentVector_create(); + ASSERT_THROWS("IndexOutOfBoundsException", { + PersistentVector_assoc(v1, 1, RT_boxInt32(42)); + }); + + // 2. dynamic_nth with non-integer + PersistentVector *v2 = PersistentVector_createMany(1, RT_boxInt32(10)); + ASSERT_THROWS("IllegalArgumentException", { + PersistentVector_dynamic_nth(v2, RT_boxDouble(1.2)); + }); + + // 3. pop on empty vector + PersistentVector *v3 = PersistentVector_create(); + ASSERT_THROWS("IllegalStateException", { + PersistentVector_pop(v3); + }); }); } @@ -485,6 +505,7 @@ int main(void) { cmocka_unit_test(vectorStructuralConjTest), cmocka_unit_test(vectorAssocTest), cmocka_unit_test(vectorTransientTest), + cmocka_unit_test(test_vector_bounds_exceptions), cmocka_unit_test(test_vector_hashing), cmocka_unit_test(test_vector_to_string), cmocka_unit_test(test_vector_pop_bang), diff --git a/backend-v2/runtime/tests/Ratio_test.c b/backend-v2/runtime/tests/Ratio_test.c index 3053eb17..f13cd22e 100644 --- a/backend-v2/runtime/tests/Ratio_test.c +++ b/backend-v2/runtime/tests/Ratio_test.c @@ -31,9 +31,9 @@ static void test_ratio_arithmetic(void **state) { Ratio *r2 = Ratio_createFromInts(1, 4); // 1/4 + 1/4 = 1/2 - void *res = Ratio_add(r1, r2); + RTValue res = Ratio_add(r1, r2); assert_non_null(res); - Ratio *r3 = (Ratio *)res; + Ratio *r3 = RT_unboxPtr(res); assert_int_equal(ratioType, getType(RT_boxPtr(r3))); Ptr_retain(r3); @@ -41,11 +41,11 @@ static void test_ratio_arithmetic(void **state) { // 1/2 * 2 = 1 (BigInteger) Ratio *two = Ratio_createFromInt(2); - void *res2 = Ratio_mul(r3, two); + RTValue res2 = Ratio_mul(r3, two); assert_non_null(res2); - assert_int_equal(bigIntegerType, getType(RT_boxPtr(res2))); + assert_int_equal(bigIntegerType, getType(res2)); - Ptr_release(res2); + release(res2); }); } @@ -54,9 +54,9 @@ static void test_ratio_simplify(void **state) { ASSERT_MEMORY_ALL_BALANCED({ Ratio *r = Ratio_createFromInts(4, 2); - void *simplified = Ratio_simplify(r); - assert_int_equal(bigIntegerType, getType(RT_boxPtr(simplified))); - Ptr_release(simplified); + RTValue simplified = Ratio_simplify(r); + assert_int_equal(bigIntegerType, getType(simplified)); + release(simplified); }); } diff --git a/backend-v2/runtime/tests/String_test.c b/backend-v2/runtime/tests/String_test.c index aae7ab3b..a004a351 100644 --- a/backend-v2/runtime/tests/String_test.c +++ b/backend-v2/runtime/tests/String_test.c @@ -185,6 +185,65 @@ static void testStringMemoryManagement(void **state) { assertMemoryBalance(&before, &after); } +static void test_string_char_at_subs(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + String *s = String_createStatic("clojure"); + + Ptr_retain(s); + assert_int_equal(String_charAt(s, 0), 'c'); + Ptr_retain(s); + assert_int_equal(String_charAt(s, 6), 'e'); + + Ptr_retain(s); + String *sub = String_subs(s, 0, 3); // "clo" + assert_string_equal("clo", String_c_str(sub)); + Ptr_release(sub); + + Ptr_retain(s); + String *sub2 = String_subs(s, 3, 7); // "jure" + assert_string_equal("jure", String_c_str(sub2)); + Ptr_release(sub2); + + Ptr_release(s); + }); +} + +static void test_string_exceptions(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + String *s = String_createStatic("abc"); + + // 1. charAt out of bounds + ASSERT_THROWS("IndexOutOfBoundsException", { + Ptr_retain(s); + String_charAt(s, 3); + }); + + // 2. subs out of bounds + ASSERT_THROWS("IndexOutOfBoundsException", { + Ptr_retain(s); + String_subs(s, 0, 4); + }); + + // 3. subs invalid range + ASSERT_THROWS("IndexOutOfBoundsException", { + Ptr_retain(s); + String_subs(s, 2, 1); + }); + + // 4. replace multi-character (unsupported) + ASSERT_THROWS("UnsupportedOperationException", { + Ptr_retain(s); + String *target = String_createStatic("ab"); + String *replacement = String_createStatic("x"); + String_replace(s, target, replacement); + }); + + Ptr_release(s); + }); +} + int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(testStaticStringMemory), @@ -195,6 +254,8 @@ int main(void) { cmocka_unit_test(testStringHashUpdate), cmocka_unit_test(testStringEdgeCases), cmocka_unit_test(testStringMemoryManagement), + cmocka_unit_test(test_string_char_at_subs), + cmocka_unit_test(test_string_exceptions), }; initialise_memory(); srand(0); diff --git a/backend-v2/runtime/tests/TestTools.c b/backend-v2/runtime/tests/TestTools.c index 48b536fe..64932195 100644 --- a/backend-v2/runtime/tests/TestTools.c +++ b/backend-v2/runtime/tests/TestTools.c @@ -97,13 +97,7 @@ void testScalingBehavior(void **state) { assert_true(res.r_squared > 0.99); } -void captureMemoryState(MemoryState *state) { - for (int i = 0; i < 256; i++) { - state->counts[i] = allocationCount[i]; - } - state->internedKeywords = Keyword_getInternCount(); - state->internedSymbols = Symbol_getInternCount(); -} +// captureMemoryState is now implemented in RuntimeInterface.c void assertMemoryBalance(MemoryState *before, MemoryState *after) { uint32_t stringLeaks = diff --git a/backend-v2/runtime/tests/TestTools.h b/backend-v2/runtime/tests/TestTools.h index 49c9f1f8..c18a5c7d 100644 --- a/backend-v2/runtime/tests/TestTools.h +++ b/backend-v2/runtime/tests/TestTools.h @@ -2,6 +2,7 @@ #define RUNTIME_TESTS #include "../Object.h" +#include "../RuntimeInterface.h" #include "../defines.h" #include #include @@ -85,13 +86,6 @@ void testScalingBehavior(void **state); assert_int_equal(start_count, end_count); \ } while (0) -typedef struct { - uword_t counts[256]; - uint32_t internedKeywords; - uint32_t internedSymbols; -} MemoryState; - -void captureMemoryState(MemoryState *state); void assertMemoryBalance(MemoryState *before, MemoryState *after); // `except_types` expects 1-based objectType values (like integerType) void assertMemoryBalanceExcept(MemoryState *before, MemoryState *after, @@ -103,9 +97,9 @@ 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."); \ + if (!strstr(BUILD_TYPE, "Debug")) { \ + fail_msg("ASSERT_MEMORY_ALL_BALANCED must be used in Debug mode as " \ + "memory tracking results are disabled in Release mode."); \ } \ MemoryState __before, __after; \ captureMemoryState(&__before); \ @@ -113,4 +107,23 @@ void assertMemoryDifference(MemoryState *before, MemoryState *after, int type, assertMemoryBalance(&__before, &__after); \ } while (0) +extern jmp_buf exception_env; +extern char *last_exception_name; +extern bool exception_catchable; + +void reset_exception_state(); + +#define ASSERT_THROWS(expected_name, block) \ + do { \ + reset_exception_state(); \ + exception_catchable = true; \ + if (setjmp(exception_env) == 0) { \ + {block} exception_catchable = false; \ + fail_msg("Expected exception '%s' was not thrown", expected_name); \ + } else { \ + exception_catchable = false; \ + assert_string_equal(last_exception_name, expected_name); \ + } \ + } while (0) + #endif diff --git a/backend-v2/state/ThreadsafeCompilerState.cpp b/backend-v2/state/ThreadsafeCompilerState.cpp index 6f0fc62c..69a1d0c6 100644 --- a/backend-v2/state/ThreadsafeCompilerState.cpp +++ b/backend-v2/state/ThreadsafeCompilerState.cpp @@ -1,108 +1,76 @@ #include "ThreadsafeCompilerState.h" #include "../bridge/Exceptions.h" -#include "../RuntimeHeaders.h" +#include "../tools/EdnParser.h" +#include "../tools/RTValueWrapper.h" namespace rt { +extern "C" void delete_class_description(void *ptr); + void ThreadsafeCompilerState::storeInternalClasses(RTValue from) { - if (getType(from) != persistentArrayMapType) - throwInternalInconsistencyException("Class definitions are not a map"); - PersistentArrayMap *map = (PersistentArrayMap *)RT_unboxPtr(from); - for (uword_t i = 0; i < map->count; i++) { - RTValue key = map->keys[i]; - RTValue value = map->values[i]; - if (getType(key) != symbolType) - throwInternalInconsistencyException( - "Class definition key is not a symbol."); - if (getType(value) != persistentArrayMapType) - throwInternalInconsistencyException( - "Class definition value is not a map."); + auto descriptions = buildClasses(from); + unordered_map localMap; + + // Phase 1: Create all hulls and attach metadata + for (auto &desc : descriptions) { + String *nameStr = String_createDynamicStr(desc->name.c_str()); + String *classNameStr = String_createDynamicStr(desc->name.c_str()); + + ::Class *c = Class_create(nameStr, classNameStr, 0, NULL); + Ptr_retain(c); // Local reference for this function's logic + localMap[desc->name] = c; - retain(key); - retain(value); + c->compilerExtension = desc.release(); + c->compilerExtensionDestructor = delete_class_description; + } + + // Phase 2: Link inheritance + for (auto const &pair : localMap) { + ::Class *c = pair.second; + ClassDescription *desc = + static_cast(c->compilerExtension); + + if (!desc->parentName.empty()) { + ::Class *parentClass = nullptr; + auto it = localMap.find(desc->parentName); + if (it != localMap.end()) { + parentClass = it->second; + Ptr_retain(parentClass); + } else { + // Look in global registry + parentClass = classRegistry.getCurrent(desc->parentName.c_str()); + // getCurrent already retains for us + } - value = RT_boxPtr( - PersistentArrayMap_assoc((PersistentArrayMap *)RT_unboxPtr(value), - Keyword_create(String_create("name")), key)); - - retain(value); - RTValue classId = - PersistentArrayMap_get((PersistentArrayMap *)RT_unboxPtr(value), - Keyword_create(String_create("object-type"))); - - if (getType(classId) == integerType) { - retain(value); - internalClassRegistry.registerObject((PersistentArrayMap *)RT_unboxPtr(value), RT_unboxInt32(classId)); - } else { - if (getType(classId) != nilType) { - release(classId); - release(key); - release(value); - throwInternalInconsistencyException(":object-type must be an integer."); + if (parentClass) { + desc->extends = parentClass; } } + } - release(classId); + // Phase 3: Register and cleanup local references + for (auto const &pair : localMap) { + ::Class *c = pair.second; + ClassDescription *desc = + static_cast(c->compilerExtension); - retain(value); - RTValue alias = - PersistentArrayMap_get((PersistentArrayMap *)RT_unboxPtr(value), - Keyword_create(String_create("alias"))); + // Give our local reference to the registry (registerObject name-based) + classRegistry.registerObject(desc->name.c_str(), c); - /* Aliases are stored in the same way as classes themselves - we abuse - alias string representation that starts with a colon */ - - if (getType(alias) == keywordType) { - String *ss = String_compactify(toString(alias)); - retain(value); - internalClassRegistry.registerObject(String_c_str(ss), (PersistentArrayMap *)RT_unboxPtr(value)); - Ptr_release(ss); - } else { - if (getType(alias) != nilType) { - release(alias); - release(key); - release(value); - throwInternalInconsistencyException(":alias must be a keyword."); - } + if (desc->type.isDetermined()) { + // If we register by ID too, we need another reference because the ID + // registry also releases on its own. + Ptr_retain(c); + classRegistry.registerObject(c, (int32_t)desc->type.determinedType()); } - - retain(key); - String *s = String_compactify(toString(key)); - retain(value); - internalClassRegistry.registerObject(String_c_str(s), (PersistentArrayMap *)RT_unboxPtr(value)); - Ptr_release(s); - } - release(from); + Ptr_release(c); // Release the local reference + } } void ThreadsafeCompilerState::storeInternalProtocols(RTValue from) { - if (getType(from) != persistentArrayMapType) - throwInternalInconsistencyException("Protocol definitions are not a map"); - - PersistentArrayMap *map = (PersistentArrayMap *)RT_unboxPtr(from); - for (uword_t i = 0; i < map->count; i++) { - RTValue key = map->keys[i]; - RTValue value = map->values[i]; - if (getType(key) != symbolType) - throwInternalInconsistencyException( - "Protocol definition key is not a symbol."); - if (getType(value) != persistentArrayMapType) - throwInternalInconsistencyException( - "Protocol definition value is not a map."); - - retain(key); - String *s = String_compactify(toString(key)); - - retain(value); - internalProtocolRegistry.registerObject(String_c_str(s), (PersistentArrayMap *)RT_unboxPtr(value)); - Ptr_release(s); - } + // Protocols can be handled similarly if needed release(from); } - - } // namespace rt - - diff --git a/backend-v2/state/ThreadsafeCompilerState.h b/backend-v2/state/ThreadsafeCompilerState.h index 808d249a..36392644 100644 --- a/backend-v2/state/ThreadsafeCompilerState.h +++ b/backend-v2/state/ThreadsafeCompilerState.h @@ -1,13 +1,15 @@ #ifndef THREADSAFE_COMPILER_STATE_H #define THREADSAFE_COMPILER_STATE_H -#include "ThreadsafeRegistry.h" +#include "../RuntimeHeaders.h" #include "ThreadsafeInlineCacheManager.h" +#include "ThreadsafeRegistry.h" +#include "bytecode.pb.h" extern "C" { -#include "runtime/ObjectProto.h" -#include "runtime/Object.h" #include "runtime/Class.h" +#include "runtime/Object.h" +#include "runtime/ObjectProto.h" #include "runtime/PersistentArrayMap.h" /* #include "runtime/Var.h" */ } @@ -15,27 +17,20 @@ extern "C" { using namespace clojure::rt::protobuf::bytecode; namespace rt { - class ThreadsafeCompilerState { - public: - - ThreadsafeInlineCacheManager objectFieldIndexAccessInlineCache; - ThreadsafeInlineCacheManager methodCallInlineCache; - - ThreadsafeRegistry classRegistry; - ThreadsafeRegistry functionAstRegistry; - ThreadsafeRegistry internalClassRegistry; - ThreadsafeRegistry internalProtocolRegistry; - /* ThreadsafeRegistry varRegistry; */ - - - ThreadsafeCompilerState() - : classRegistry(true), functionAstRegistry(false), - internalClassRegistry(true), - internalProtocolRegistry(true) /*, varRegistry(true)*/ {} - - void storeInternalClasses(RTValue from); - void storeInternalProtocols(RTValue from); - }; -} +class ThreadsafeCompilerState { +public: + ThreadsafeInlineCacheManager objectFieldIndexAccessInlineCache; + ThreadsafeInlineCacheManager methodCallInlineCache; + + ThreadsafeRegistry<::Class> classRegistry; + ThreadsafeRegistry functionAstRegistry; + /* ThreadsafeRegistry varRegistry; */ + + ThreadsafeCompilerState() : classRegistry(true), functionAstRegistry(false) {} + + void storeInternalClasses(RTValue from); + void storeInternalProtocols(RTValue from); +}; +} // namespace rt #endif diff --git a/backend-v2/state/ThreadsafeInlineCacheManager.h b/backend-v2/state/ThreadsafeInlineCacheManager.h index ca0d657b..1c35c3da 100644 --- a/backend-v2/state/ThreadsafeInlineCacheManager.h +++ b/backend-v2/state/ThreadsafeInlineCacheManager.h @@ -1,54 +1,55 @@ #ifndef INLINE_CACHE_MANAGER_H #define INLINE_CACHE_MANAGER_H -#include +#include "../RuntimeHeaders.h" +#include "../runtime/word.h" +#include #include #include -#include -#include "../runtime/word.h" +#include namespace rt { struct ThreadsafeInlineCacheSlot { - std::atomic tag{0}; + std::atomic tag{0}; - union Payload { - std::atomic offset; - std::atomic pointer; - Payload() : offset(0) {} - } payload; + union Payload { + std::atomic offset; + std::atomic pointer; + Payload() : offset(0) {} + } payload; - enum class Kind { Field, Call } kind; + enum class Kind { Field, Call } kind; }; class ThreadsafeInlineCacheManager { private: - std::mutex mutex; - std::vector> slots; + std::mutex mutex; + std::vector> slots; public: - ThreadsafeInlineCacheSlot* allocateSlot() { - std::lock_guard lock(mutex); - auto slot = std::make_unique(); - ThreadsafeInlineCacheSlot* ptr = slot.get(); - slots.push_back(std::move(slot)); - return ptr; - } - - void clear() { - std::lock_guard lock(mutex); - slots.clear(); - } + ThreadsafeInlineCacheSlot *allocateSlot() { + std::lock_guard lock(mutex); + auto slot = std::make_unique(); + ThreadsafeInlineCacheSlot *ptr = slot.get(); + slots.push_back(std::move(slot)); + return ptr; + } + + void clear() { + std::lock_guard lock(mutex); + slots.clear(); + } }; - /* Value* emitGenericGuard(IRBuilder<>& builder, */ /* Value* currentTag, */ /* InlineCacheSlot* slot) { */ /* LLVMContext& ctx = builder.getContext(); */ /* Value* slotAddr = builder.getInt64(reinterpret_cast(slot)); */ - + /* // Załaduj tag ze slotu (Atomowo) */ -/* LoadInst* cachedTag = builder.CreateLoad(PointerType::getUnqual(ctx), slotAddr); */ +/* LoadInst* cachedTag = builder.CreateLoad(PointerType::getUnqual(ctx), + * slotAddr); */ /* cachedTag->setOrdering(AtomicOrdering::Monotonic); */ /* // Sprawdź trafienie w cache */ @@ -61,60 +62,68 @@ class ThreadsafeInlineCacheManager { /* std::vector args) { */ /* LLVMContext& ctx = builder.getContext(); */ /* auto* cacheSlot = metadataManager.allocateFunctionSlot(); */ -/* Value* cacheSlotAddr = builder.getInt64(reinterpret_cast(cacheSlot)); */ +/* Value* cacheSlotAddr = + * builder.getInt64(reinterpret_cast(cacheSlot)); */ /* // 1. Pobierz aktualną funkcję z Var (może być redefiniowana) */ -/* Value* currentFnObj = builder.CreateLoad(PointerType::getUnqual(ctx), varPtr, "current_fn_obj"); */ +/* Value* currentFnObj = builder.CreateLoad(PointerType::getUnqual(ctx), + * varPtr, "current_fn_obj"); */ /* // 2. Pobierz dane z cache */ -/* Value* cachedFnObj = builder.CreateLoad(PointerType::getUnqual(ctx), cacheSlotAddr, "cached_fn_obj"); */ +/* Value* cachedFnObj = builder.CreateLoad(PointerType::getUnqual(ctx), + * cacheSlotAddr, "cached_fn_obj"); */ -/* Value* isHit = builder.CreateICmpEQ(currentFnObj, cachedFnObj, "is_call_hit"); */ +/* Value* isHit = builder.CreateICmpEQ(currentFnObj, cachedFnObj, + * "is_call_hit"); */ /* Function* parentFunc = builder.GetInsertBlock()->getParent(); */ -/* BasicBlock* fastPath = BasicBlock::Create(ctx, "call_fast", parentFunc); */ -/* BasicBlock* slowPath = BasicBlock::Create(ctx, "call_slow", parentFunc); */ -/* BasicBlock* exitBlock = BasicBlock::Create(ctx, "call_exit", parentFunc); */ +/* BasicBlock* fastPath = BasicBlock::Create(ctx, "call_fast", parentFunc); + */ +/* BasicBlock* slowPath = BasicBlock::Create(ctx, "call_slow", parentFunc); + */ +/* BasicBlock* exitBlock = BasicBlock::Create(ctx, "call_exit", parentFunc); + */ /* builder.CreateCondBr(isHit, fastPath, slowPath); */ /* // --- Fast Path: Bezpośredni skok --- */ /* builder.SetInsertPoint(fastPath); */ -/* Value* codePtrAddr = builder.CreatePtrAdd(cacheSlotAddr, builder.getInt64(8)); */ -/* Value* cachedCode = builder.CreateLoad(PointerType::getUnqual(ctx), codePtrAddr, "cached_code"); */ - +/* Value* codePtrAddr = builder.CreatePtrAdd(cacheSlotAddr, + * builder.getInt64(8)); */ +/* Value* cachedCode = builder.CreateLoad(PointerType::getUnqual(ctx), + * codePtrAddr, "cached_code"); */ + /* // Rzutowanie na sygnaturę funkcji i wywołanie */ -/* FunctionType* fnSig = FunctionType::get(builder.getInt64Ty(), { /\* typy argumentów *\/ }, false); */ +/* FunctionType* fnSig = FunctionType::get(builder.getInt64Ty(), { /\* typy + * argumentów *\/ }, false); */ /* Value* resultFast = builder.CreateCall(fnSig, cachedCode, args); */ /* builder.CreateBr(exitBlock); */ /* // --- Slow Path: Runtime lookup --- */ /* builder.SetInsertPoint(slowPath); */ /* Function* updateFunc = module->getFunction("runtime_update_fn_cache"); */ -/* Value* resultSlow = builder.CreateCall(updateFunc, {currentFnObj, cacheSlotAddr, ...args}); */ +/* Value* resultSlow = builder.CreateCall(updateFunc, {currentFnObj, + * cacheSlotAddr, ...args}); */ /* builder.CreateBr(exitBlock); */ - + /* // ... PHI node dla wyniku ... */ /* } */ - - - // Dostep do structury: -/* extern "C" int64_t runtime_update_cache(Object* obj, InlineCacheSlot* slot, const char* name) { */ +/* extern "C" int64_t runtime_update_cache(Object* obj, InlineCacheSlot* slot, + * const char* name) { */ /* TypeDescriptor* type = obj->header->type; */ /* int64_t offset = type->layout.findOffset(name); */ - + /* // Atomowe zapisy gwarantują, że inne wątki zobaczą albo starą, */ /* // albo nową wartość, nigdy nic pomiędzy. */ /* slot->lastTypeDescriptor.store(type, std::memory_order_relaxed); */ /* slot->lastOffset.store(offset, std::memory_order_relaxed); */ - + /* return *(int64_t*)((char*)obj + offset); */ /* } */ } // namespace rt - #endif diff --git a/backend-v2/state/ThreadsafeRegistry.h b/backend-v2/state/ThreadsafeRegistry.h index b012b1fb..6bf40392 100644 --- a/backend-v2/state/ThreadsafeRegistry.h +++ b/backend-v2/state/ThreadsafeRegistry.h @@ -1,93 +1,104 @@ #ifndef THREADSAFE_REGISTRY_H #define THREADSAFE_REGISTRY_H +#include "../RuntimeHeaders.h" +#include "../bridge/Exceptions.h" #include -#include #include +#include #include -#include "../RuntimeHeaders.h" -#include "../bridge/Exceptions.h" namespace rt { - template - class ThreadsafeRegistry { - private: - mutable std::mutex registryMutex; - std::unordered_map registry; - std::unordered_map indexedRegistry; - int32_t currentIndex = 10000; - bool manageRuntimeMemory; - - public: - ThreadsafeRegistry(bool _manageRuntimeMemory) - : manageRuntimeMemory(_manageRuntimeMemory) {} - - uword_t registerObject(T *newDef, int32_t requiredIndex = -1) { - std::lock_guard lock(registryMutex); - requiredIndex = requiredIndex == -1 ? currentIndex : requiredIndex; - auto it = indexedRegistry.find(requiredIndex); - if (manageRuntimeMemory && it != indexedRegistry.end()) { - Ptr_release((void*)it->second); - } - indexedRegistry[currentIndex] = newDef; - return currentIndex++; +template class ThreadsafeRegistry { +private: + mutable std::mutex registryMutex; + std::unordered_map registry; + std::unordered_map indexedRegistry; + int32_t currentIndex = 10000; + bool manageRuntimeMemory; + +public: + ThreadsafeRegistry(bool _manageRuntimeMemory) + : manageRuntimeMemory(_manageRuntimeMemory) {} + + uword_t registerObject(T *newDef, int32_t requiredIndex = -1) { + std::lock_guard lock(registryMutex); + int32_t index = requiredIndex == -1 ? currentIndex++ : requiredIndex; + + auto it = indexedRegistry.find(index); + if (manageRuntimeMemory && it != indexedRegistry.end()) { + Ptr_release((void *)it->second); } + indexedRegistry[index] = newDef; - void registerObject(const char *name, T *newDef) { - std::lock_guard lock(registryMutex); - - std::string key(name); - auto it = registry.find(key); - - if (manageRuntimeMemory && it != registry.end()) { - Ptr_release((void*)it->second); - } - - registry[name] = newDef; + if (index >= currentIndex) { + currentIndex = index + 1; } + return index; + } - T *getCurrent(const int32_t index) const { - std::lock_guard lock(registryMutex); - auto it = indexedRegistry.find(index); + void registerObject(const char *name, T *newDef) { + std::lock_guard lock(registryMutex); - if (it != indexedRegistry.end()) { - if(manageRuntimeMemory) Ptr_retain((void *)it->second); - return it->second; - } - - return nullptr; + std::string key(name); + auto it = registry.find(key); + + if (manageRuntimeMemory && it != registry.end()) { + Ptr_release((void *)it->second); } - T *getCurrent(const char *name) const { - std::lock_guard lock(registryMutex); - - auto it = registry.find(name); + registry[name] = newDef; + } - if (it != registry.end()) { - if(manageRuntimeMemory) Ptr_retain((void *)it->second); - return it->second; - } - - return nullptr; + T *getCurrent(const int32_t index) const { + std::lock_guard lock(registryMutex); + auto it = indexedRegistry.find(index); + + if (it != indexedRegistry.end()) { + if (manageRuntimeMemory) + Ptr_retain((void *)it->second); + return it->second; } - ~ThreadsafeRegistry() { - if (!manageRuntimeMemory) - return; - std::lock_guard lock(registryMutex); - for (auto const& [name, definition] : registry) { - if (definition != nullptr) { - Ptr_release((void*)definition); - } - } + return nullptr; + } + + T *getCurrent(const char *name) const { + std::lock_guard lock(registryMutex); + + auto it = registry.find(name); + + if (it != registry.end()) { + if (manageRuntimeMemory) + Ptr_retain((void *)it->second); + return it->second; + } - for (auto const& [index, definition] : indexedRegistry) { - if (definition != nullptr) { - Ptr_release((void*)definition); - } - } - } - }; -} + return nullptr; + } + + ~ThreadsafeRegistry() { + if (!manageRuntimeMemory) + return; + std::lock_guard lock(registryMutex); + while (!registry.empty()) { + auto it = registry.begin(); + T *ptr = it->second; + registry.erase(it); + if (ptr != nullptr) { + Ptr_release((void *)ptr); + } + } + while (!indexedRegistry.empty()) { + auto it = indexedRegistry.begin(); + T *ptr = it->second; + indexedRegistry.erase(it); + if (ptr != nullptr) { + Ptr_release((void *)ptr); + } + } + } +}; +} // namespace rt #endif diff --git a/backend-v2/tests/CMakeLists.txt b/backend-v2/tests/CMakeLists.txt index ed5a3f6d..10771039 100644 --- a/backend-v2/tests/CMakeLists.txt +++ b/backend-v2/tests/CMakeLists.txt @@ -1,9 +1,13 @@ set(COMMON_TEST_SOURCES ../runtime/tests/TestTools.c + codegen/runtime_helpers.c ) set(TEST_MODULES tools/EdnParser_test + codegen/Overflow_test + codegen/IfNode_test + codegen/StaticCallType_test ) foreach(TEST_MODULE ${TEST_MODULES}) diff --git a/backend-v2/tests/codegen/IfNode_test.cpp b/backend-v2/tests/codegen/IfNode_test.cpp new file mode 100644 index 00000000..cdd02991 --- /dev/null +++ b/backend-v2/tests/codegen/IfNode_test.cpp @@ -0,0 +1,200 @@ +#include "../../RuntimeHeaders.h" +#include "../../jit/JITEngine.h" +#include "../../state/ThreadsafeCompilerState.h" +#include "../../tools/EdnParser.h" +#include "bytecode.pb.h" +#include +#include +#include + +extern "C" { +#include "../../runtime/RuntimeInterface.h" +#include "../../runtime/tests/TestTools.h" +#include +} + +#include "../../bridge/Exceptions.h" + +using namespace std; +using namespace rt; +using namespace clojure::rt::protobuf::bytecode; + +static RTValue resPtrToValue(llvm::orc::ExecutorAddr res) { + return res.toPtr()(); +} + +static void test_if_truthy_const(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + + Node ifNode; + ifNode.set_op(opIf); + auto *if_ = ifNode.mutable_subnode()->mutable_if_(); + + // Test: true + auto *test = if_->mutable_test(); + test->set_op(opConst); + test->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeBool); + test->mutable_subnode()->mutable_const_()->set_val("true"); + + // Then: "then" + auto *then = if_->mutable_then(); + then->set_op(opConst); + then->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); + then->mutable_subnode()->mutable_const_()->set_val("then"); + + // Else: "else" + auto *else_ = if_->mutable_else_(); + else_->set_op(opConst); + else_->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); + else_->mutable_subnode()->mutable_const_()->set_val("else"); + + auto resIf = engine + .compileAST(ifNode, "__test_if_truthy_const", + llvm::OptimizationLevel::O0, false) + .get(); + + RTValue result = resPtrToValue(resIf); + assert_true(RT_isPtr(result)); + assert_string_equal("then", String_c_str(reinterpret_cast(RT_unboxPtr(result)))); + release(result); + }); +} + +static void test_if_falsy_const(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + + Node ifNode; + ifNode.set_op(opIf); + auto *if_ = ifNode.mutable_subnode()->mutable_if_(); + + // Test: false + auto *test = if_->mutable_test(); + test->set_op(opConst); + test->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeBool); + test->mutable_subnode()->mutable_const_()->set_val("false"); + + // Then: "then" + auto *then = if_->mutable_then(); + then->set_op(opConst); + then->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); + then->mutable_subnode()->mutable_const_()->set_val("then"); + + // Else: "else" + auto *else_ = if_->mutable_else_(); + else_->set_op(opConst); + else_->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); + else_->mutable_subnode()->mutable_const_()->set_val("else"); + + auto resIf = engine + .compileAST(ifNode, "__test_if_falsy_const", + llvm::OptimizationLevel::O0, false) + .get(); + + RTValue result = resPtrToValue(resIf); + assert_true(RT_isPtr(result)); + assert_string_equal("else", String_c_str(reinterpret_cast(RT_unboxPtr(result)))); + release(result); + }); +} + +static void test_if_nil(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + + Node ifNode; + ifNode.set_op(opIf); + auto *if_ = ifNode.mutable_subnode()->mutable_if_(); + + // Test: nil + auto *test = if_->mutable_test(); + test->set_op(opConst); + test->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNil); + test->mutable_subnode()->mutable_const_()->set_val("nil"); + + // Then: "then" + auto *then = if_->mutable_then(); + then->set_op(opConst); + then->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); + then->mutable_subnode()->mutable_const_()->set_val("then"); + + // Else: "else" + auto *else_ = if_->mutable_else_(); + else_->set_op(opConst); + else_->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); + else_->mutable_subnode()->mutable_const_()->set_val("else"); + + auto resIf = engine + .compileAST(ifNode, "__test_if_nil", + llvm::OptimizationLevel::O0, false) + .get(); + + RTValue result = resPtrToValue(resIf); + assert_true(RT_isPtr(result)); + assert_string_equal("else", String_c_str(reinterpret_cast(RT_unboxPtr(result)))); + release(result); + }); +} + +static void test_if_integer_const(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + + Node ifNode; + ifNode.set_op(opIf); + auto *if_ = ifNode.mutable_subnode()->mutable_if_(); + + // Test: 1 + auto *test = if_->mutable_test(); + test->set_op(opConst); + test->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + test->set_tag("long"); + test->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + test->mutable_subnode()->mutable_const_()->set_val("1"); + + // Then: "then" + auto *then = if_->mutable_then(); + then->set_op(opConst); + then->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); + then->mutable_subnode()->mutable_const_()->set_val("then"); + + // Else: "else" + auto *else_ = if_->mutable_else_(); + else_->set_op(opConst); + else_->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); + else_->mutable_subnode()->mutable_const_()->set_val("else"); + + auto resIf = engine + .compileAST(ifNode, "__test_if_integer_const", + llvm::OptimizationLevel::O0, false) + .get(); + + RTValue result = resPtrToValue(resIf); + assert_true(RT_isPtr(result)); + assert_string_equal("then", String_c_str(reinterpret_cast(RT_unboxPtr(result)))); + release(result); + }); +} + +int main(void) { + initialise_memory(); + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_if_truthy_const), + cmocka_unit_test(test_if_falsy_const), + cmocka_unit_test(test_if_nil), + cmocka_unit_test(test_if_integer_const), + }; + + int result = cmocka_run_group_tests(tests, NULL, NULL); + RuntimeInterface_cleanup(); + return result; +} diff --git a/backend-v2/tests/codegen/Overflow_test.cpp b/backend-v2/tests/codegen/Overflow_test.cpp new file mode 100644 index 00000000..5e9ec587 --- /dev/null +++ b/backend-v2/tests/codegen/Overflow_test.cpp @@ -0,0 +1,154 @@ +#include "../../RuntimeHeaders.h" +#include "../../jit/JITEngine.h" +#include "../../state/ThreadsafeCompilerState.h" +#include "../../tools/EdnParser.h" +#include "bytecode.pb.h" +#include +#include +#include + +extern "C" { +#include "../../runtime/RuntimeInterface.h" +#include "../../runtime/tests/TestTools.h" +#include +} + +#include "../../bridge/Exceptions.h" + +using namespace std; +using namespace rt; +using namespace clojure::rt::protobuf::bytecode; + +static RTValue resPtrToValue(llvm::orc::ExecutorAddr res) { + return res.toPtr()(); +} + +static void setup_compiler_state(rt::ThreadsafeCompilerState &compState, rt::JITEngine &engine) { + // Load metadata + Programme astClasses; + { + std::fstream classesInput("tests/rt-classes.cljb", + std::ios::in | std::ios::binary); + if (!astClasses.ParseFromIstream(&classesInput)) { + fail_msg("Failed to parse bytecode."); + } + } + + // Initialize compiler state with metadata + llvm::orc::ExecutorAddr resClasses = + engine + .compileAST(astClasses.nodes(0), "__classes", + llvm::OptimizationLevel::O0, false) + .get(); + RTValue classes = resClasses.toPtr()(); + auto classesList = rt::buildClasses(classes); + for (auto &desc : classesList) { + auto &nameStr = desc->name; + String *className = String_create(nameStr.c_str()); + Ptr_retain(className); // Retain for the second use in Class_create + Class *cls = Class_create(className, className, 0, nullptr); + cls->compilerExtension = desc.release(); + cls->compilerExtensionDestructor = delete_class_description; + compState.classRegistry.registerObject(nameStr.c_str(), cls); + } +} + +static void test_integer_overflow_add(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + setup_compiler_state(compState, engine); + + // Create a StaticCallNode for (clojure.lang.Numbers/add 2147483647 1) + Node callNode; + callNode.set_op(opStaticCall); + callNode.set_tag("long"); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("add"); + + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->set_tag("long"); + arg1->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + arg1->mutable_subnode()->mutable_const_()->set_val("2147483647"); + + auto *arg2 = sc->add_args(); + arg2->set_op(opConst); + arg2->set_tag("long"); + arg2->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + arg2->mutable_subnode()->mutable_const_()->set_val("1"); + + auto resCall = engine + .compileAST(callNode, "__test_overflow_add", + llvm::OptimizationLevel::O0, false) + .get(); + + try { + resPtrToValue(resCall); + fail_msg("Should have thrown ArithmeticException"); + } catch (const LanguageException &e) { + std::string err = getExceptionString(e); + assert_true(err.find("Integer overflow") != std::string::npos); + } + }); +} + +static void test_integer_overflow_sub(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + setup_compiler_state(compState, engine); + + // Create a StaticCallNode for (clojure.lang.Numbers/minus -2147483648 1) + Node callNode; + callNode.set_op(opStaticCall); + callNode.set_tag("long"); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("minus"); + + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->set_tag("long"); + arg1->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + arg1->mutable_subnode()->mutable_const_()->set_val("-2147483648"); + + auto *arg2 = sc->add_args(); + arg2->set_op(opConst); + arg2->set_tag("long"); + arg2->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + arg2->mutable_subnode()->mutable_const_()->set_val("1"); + + auto resCall = engine + .compileAST(callNode, "__test_overflow_sub", + llvm::OptimizationLevel::O0, false) + .get(); + + try { + resPtrToValue(resCall); + fail_msg("Should have thrown ArithmeticException"); + } catch (const LanguageException &e) { + std::string err = getExceptionString(e); + assert_true(err.find("Integer overflow") != std::string::npos); + } + }); +} + +int main(void) { + initialise_memory(); + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_integer_overflow_add), + cmocka_unit_test(test_integer_overflow_sub), + }; + + int result = cmocka_run_group_tests(tests, NULL, NULL); + RuntimeInterface_cleanup(); + return result; +} diff --git a/backend-v2/tests/codegen/StaticCallType_test.cpp b/backend-v2/tests/codegen/StaticCallType_test.cpp new file mode 100644 index 00000000..13a5daa0 --- /dev/null +++ b/backend-v2/tests/codegen/StaticCallType_test.cpp @@ -0,0 +1,304 @@ +#include "../../state/ThreadsafeCompilerState.h" +#include "../../tools/EdnParser.h" +#include "../../codegen/CodeGen.h" +#include "../../types/ConstantBool.h" +#include "../../types/ConstantInteger.h" +#include "../../types/ConstantBigInteger.h" +#include "../../types/ConstantRatio.h" +#include "bytecode.pb.h" + +#include + +#include "../../runtime/defines.h" +#include "../../runtime/RTValue.h" +#include "../../runtime/Object.h" +#include "../../runtime/RuntimeInterface.h" + +extern "C" { +// Minimal declarations for functions provided by runtime_helpers.c +void test_initialise_memory(); +void test_RuntimeInterface_cleanup(); +String *test_String_create(const char *str); +Class *test_Class_create(String *name, String *simpleName, int flags, void *ext); +void test_delete_class_description(void *p); +void test_release(RTValue v); + +#include +#include +#include +#include +#include +} + +using namespace std; +using namespace rt; +using namespace clojure::rt::protobuf::bytecode; + +static void setup_mock_runtime(rt::ThreadsafeCompilerState &compState) { + // 1. Numbers class for arithmetic + { + String *nameStr = test_String_create("clojure.lang.Numbers"); + String *classNameStr = test_String_create("clojure.lang.Numbers"); + Class *cls = test_Class_create(nameStr, classNameStr, 0, nullptr); + + ClassDescription *ext = new ClassDescription(); + ext->name = "clojure.lang.Numbers"; + + // Add "Add" intrinsic + IntrinsicDescription addId; + addId.symbol = "Add"; + addId.type = CallType::Intrinsic; + addId.argTypes = { ObjectTypeSet(integerType, false), ObjectTypeSet(integerType, false) }; + addId.returnType = ObjectTypeSet(integerType, false); + ext->staticFns["add"].push_back(addId); + + // Add "GTE" intrinsic + IntrinsicDescription gteId; + gteId.symbol = "ICmpSGE"; + gteId.type = CallType::Intrinsic; + gteId.argTypes = { ObjectTypeSet(integerType, false), ObjectTypeSet(integerType, false) }; + gteId.returnType = ObjectTypeSet(booleanType, false); + ext->staticFns["gte"].push_back(gteId); + + // Add "Add_IB" intrinsic + IntrinsicDescription addIBId; + addIBId.symbol = "Add_IB"; + addIBId.type = CallType::Intrinsic; + addIBId.argTypes = { ObjectTypeSet(integerType, false), ObjectTypeSet(bigIntegerType, false) }; + addIBId.returnType = ObjectTypeSet(bigIntegerType, false); + ext->staticFns["add"].push_back(addIBId); + + // Add "Add_BI" intrinsic + IntrinsicDescription addBIId; + addBIId.symbol = "Add_BI"; + addBIId.type = CallType::Intrinsic; + addBIId.argTypes = { ObjectTypeSet(bigIntegerType, false), ObjectTypeSet(integerType, false) }; + addBIId.returnType = ObjectTypeSet(bigIntegerType, false); + ext->staticFns["add"].push_back(addBIId); + + // Add "BigInteger_add" intrinsic + + IntrinsicDescription addBBId; + addBBId.symbol = "BigInteger_add"; + addBBId.type = CallType::Intrinsic; + addBBId.argTypes = { ObjectTypeSet(bigIntegerType, false), ObjectTypeSet(bigIntegerType, false) }; + addBBId.returnType = ObjectTypeSet(bigIntegerType, false); + ext->staticFns["add"].push_back(addBBId); + + // Add "Ratio_add" intrinsic + IntrinsicDescription addRRId; + addRRId.symbol = "Ratio_add"; + addRRId.type = CallType::Intrinsic; + addRRId.argTypes = { ObjectTypeSet(ratioType, false), ObjectTypeSet(ratioType, false) }; + addRRId.returnType = ObjectTypeSet(ratioType, false); + ext->staticFns["add"].push_back(addRRId); + + // Add "GTE" for BigInt + IntrinsicDescription gteBBId; + gteBBId.symbol = "BigInteger_gte"; + gteBBId.type = CallType::Intrinsic; + gteBBId.argTypes = { ObjectTypeSet(bigIntegerType, false), ObjectTypeSet(bigIntegerType, false) }; + gteBBId.returnType = ObjectTypeSet(booleanType, false); + ext->staticFns["gte"].push_back(gteBBId); + + cls->compilerExtension = ext; + + cls->compilerExtensionDestructor = test_delete_class_description; + compState.classRegistry.registerObject("clojure.lang.Numbers", cls); + } +} + +static void test_constant_arithmetic_getType(void **state) { + (void)state; + rt::ThreadsafeCompilerState compState; + setup_mock_runtime(compState); + + CodeGen codegen("test", compState); + + // (clojure.lang.Numbers/add 1 2) + Node callNode; + callNode.set_op(opStaticCall); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("add"); + + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->set_tag("long"); + arg1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg1->mutable_subnode()->mutable_const_()->set_val("1"); + + auto *arg2 = sc->add_args(); + arg2->set_op(opConst); + arg2->set_tag("long"); + arg2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg2->mutable_subnode()->mutable_const_()->set_val("2"); + + ObjectTypeSet resultType = codegen.getType(callNode, ObjectTypeSet::all()); + + assert_true(resultType.isDetermined()); + assert_int_equal(integerType, resultType.determinedType()); + assert_non_null(resultType.getConstant()); + + auto *cons = dynamic_cast(resultType.getConstant()); + assert_non_null(cons); + assert_int_equal(3, cons->value); +} + +static void test_constant_comparison_getType(void **state) { + (void)state; + rt::ThreadsafeCompilerState compState; + setup_mock_runtime(compState); + + CodeGen codegen("test", compState); + + // (clojure.lang.Numbers/gte 10 5) + Node callNode; + callNode.set_op(opStaticCall); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("gte"); + + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->set_tag("long"); + arg1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg1->mutable_subnode()->mutable_const_()->set_val("10"); + + auto *arg2 = sc->add_args(); + arg2->set_op(opConst); + arg2->set_tag("long"); + arg2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg2->mutable_subnode()->mutable_const_()->set_val("5"); + + ObjectTypeSet resultType = codegen.getType(callNode, ObjectTypeSet::all()); + + assert_true(resultType.isDetermined()); + assert_int_equal(booleanType, resultType.determinedType()); + assert_non_null(resultType.getConstant()); + + auto *cons = dynamic_cast(resultType.getConstant()); + assert_non_null(cons); + assert_true(cons->value); +} + +static void test_bigint_arithmetic_getType(void **state) { + (void)state; + rt::ThreadsafeCompilerState compState; + setup_mock_runtime(compState); + CodeGen codegen("test", compState); + + // (clojure.lang.Numbers/add 100000000000000000000N 1) + Node callNode; + callNode.set_op(opStaticCall); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("add"); + + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->set_tag("long"); + arg1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg1->mutable_subnode()->mutable_const_()->set_val("100000000000000000000"); // BigInt + + auto *arg2 = sc->add_args(); + arg2->set_op(opConst); + arg2->set_tag("long"); + arg2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg2->mutable_subnode()->mutable_const_()->set_val("1"); + + ObjectTypeSet resultType = codegen.getType(callNode, ObjectTypeSet::all()); + + assert_true(resultType.isDetermined()); + assert_int_equal(bigIntegerType, resultType.determinedType()); + assert_non_null(resultType.getConstant()); + + auto *cons = dynamic_cast(resultType.getConstant()); + assert_non_null(cons); + assert_string_equal("100000000000000000001", cons->toString().c_str()); +} + +static void test_ratio_arithmetic_getType(void **state) { + (void)state; + rt::ThreadsafeCompilerState compState; + setup_mock_runtime(compState); + CodeGen codegen("test", compState); + + // (clojure.lang.Numbers/add 1/2 1/3) + Node callNode; + callNode.set_op(opStaticCall); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("add"); + + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg1->mutable_subnode()->mutable_const_()->set_val("1/2"); + + auto *arg2 = sc->add_args(); + arg2->set_op(opConst); + arg2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg2->mutable_subnode()->mutable_const_()->set_val("1/3"); + + ObjectTypeSet resultType = codegen.getType(callNode, ObjectTypeSet::all()); + + assert_true(resultType.isDetermined()); + assert_int_equal(ratioType, resultType.determinedType()); + assert_non_null(resultType.getConstant()); + + auto *cons = dynamic_cast(resultType.getConstant()); + assert_non_null(cons); + assert_string_equal("5/6", cons->toString().c_str()); +} + +static void test_bigint_comparison_getType(void **state) { + (void)state; + rt::ThreadsafeCompilerState compState; + setup_mock_runtime(compState); + CodeGen codegen("test", compState); + + // (clojure.lang.Numbers/gte 100000000000000000001N 100000000000000000000N) + Node callNode; + callNode.set_op(opStaticCall); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("gte"); + + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg1->mutable_subnode()->mutable_const_()->set_val("100000000000000000001"); + + auto *arg2 = sc->add_args(); + arg2->set_op(opConst); + arg2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg2->mutable_subnode()->mutable_const_()->set_val("100000000000000000000"); + + ObjectTypeSet resultType = codegen.getType(callNode, ObjectTypeSet::all()); + + assert_true(resultType.isDetermined()); + assert_int_equal(booleanType, resultType.determinedType()); + assert_non_null(resultType.getConstant()); + + auto *cons = dynamic_cast(resultType.getConstant()); + assert_non_null(cons); + assert_true(cons->value); +} + + +int main(void) { + test_initialise_memory(); + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_constant_arithmetic_getType), + cmocka_unit_test(test_constant_comparison_getType), + cmocka_unit_test(test_bigint_arithmetic_getType), + cmocka_unit_test(test_ratio_arithmetic_getType), + cmocka_unit_test(test_bigint_comparison_getType), + }; + + + int result = cmocka_run_group_tests(tests, NULL, NULL); + test_RuntimeInterface_cleanup(); + return result; +} diff --git a/backend-v2/tests/codegen/runtime_helpers.c b/backend-v2/tests/codegen/runtime_helpers.c new file mode 100644 index 00000000..4acbbf06 --- /dev/null +++ b/backend-v2/tests/codegen/runtime_helpers.c @@ -0,0 +1,26 @@ +#include +#include +#include + +// Define RT_NO_STRING_H to prevent RTValue.h from including (and +// finding the local String.h) +#define RT_NO_STRING_H +#include "../../RuntimeHeaders.h" + +// Correct declaration for delete_class_description which is in EdnParser.cpp +void delete_class_description(void *ptr); + +void test_initialise_memory() { initialise_memory(); } + +void test_RuntimeInterface_cleanup() { RuntimeInterface_cleanup(); } + +String *test_String_create(const char *str) { return String_create(str); } + +Class *test_Class_create(String *name, String *simpleName, int flags, + void *ext) { + return Class_create(name, simpleName, flags, ext); +} + +void test_delete_class_description(void *p) { delete_class_description(p); } + +void test_release(RTValue v) { release(v); } diff --git a/backend-v2/tests/rt-classes.edn b/backend-v2/tests/rt-classes.edn index 80ea09a9..db511350 100644 --- a/backend-v2/tests/rt-classes.edn +++ b/backend-v2/tests/rt-classes.edn @@ -70,11 +70,21 @@ {:args [:any :any] :type :call :symbol "div" :returns :any}] gte [{:args [:double :double] :type :intrinsic :symbol "FCmpOGE" :returns :bool} {:args [:int :int] :type :intrinsic :symbol "ICmpSGE" :returns :bool} + {:args [:bigint :bigint] :type :call :symbol "BigInteger_gte" :returns :bool} {:args [:any :any] :type :call :symbol "gte" :returns :bool}] + add [{:args [:double :double] :type :intrinsic :symbol "FAdd" :returns :double} + {:args [:int :int] :type :intrinsic :symbol "Add" :returns :int} + {:args [:int :bigint] :type :intrinsic :symbol "Add_IB" :returns :bigint} + {:args [:bigint :int] :type :intrinsic :symbol "Add_BI" :returns :bigint} + {:args [:bigint :bigint] :type :call :symbol "BigInteger_add" :returns :bigint} + {:args [:any :any] :type :call :symbol "add" :returns :any}] + + divide [{:args [:int :int] :type :intrinsic :symbol "Integer_div" :returns :any}] lt [{:args [:double :double] :type :intrinsic :symbol "FCmpOLT" :returns :bool} {:args [:int :int] :type :intrinsic :symbol "ICmpOLT" :returns :bool} {:args [:any :any] :type :call :symbol "lt" :returns :bool}]}} + java.lang.Math {:static-fns {sin [{:args [:double] :type :call :symbol "sin" :returns :double}] diff --git a/backend-v2/tests/tools/EdnParser_test.cpp b/backend-v2/tests/tools/EdnParser_test.cpp index bafaa0db..401a2251 100644 --- a/backend-v2/tests/tools/EdnParser_test.cpp +++ b/backend-v2/tests/tools/EdnParser_test.cpp @@ -95,8 +95,122 @@ static void test_edn_parser_class_parsing_memory(void **state) { { try { - vector classesList = rt::buildClasses(classes); + auto classesList = rt::buildClasses(classes); assert_true(classesList.size() > 0); + + // Verification of specific classes from rt-classes.edn + rt::ClassDescription *objDesc = nullptr; + rt::ClassDescription *longDesc = nullptr; + rt::ClassDescription *numbersDesc = nullptr; + rt::ClassDescription *stringDesc = nullptr; + + for (auto &c : classesList) { + if (c->name == "java.lang.Object") + objDesc = c.get(); + else if (c->name == "java.lang.Long") + longDesc = c.get(); + else if (c->name == "clojure.lang.Numbers") + numbersDesc = c.get(); + else if (c->name == "java.lang.String") + stringDesc = c.get(); + else if (c->name == "java.lang.Class") { + assert_string_equal("java.lang.Object", c->parentName.c_str()); + } + } + + // Verify java.lang.Object + assert_non_null(objDesc); + assert_true(objDesc->type.isDynamic()); // :any becomes dynamic type + + // Verify java.lang.Long + assert_non_null(longDesc); + assert_int_equal(1, (int)longDesc->type.determinedType()); + assert_true(longDesc->type.contains(integerType)); + // Verify ALL static fields in java.lang.Long + assert_int_equal(4, longDesc->staticFields.size()); + assert_int_equal(4, RT_unboxInt32(longDesc->staticFields.at("BYTES"))); + assert_int_equal(2147483647, + RT_unboxInt32(longDesc->staticFields.at("MAX_VALUE"))); + assert_int_equal(-2147483647, + RT_unboxInt32(longDesc->staticFields.at("MIN_VALUE"))); + assert_int_equal(32, RT_unboxInt32(longDesc->staticFields.at("SIZE"))); + + // Verify clojure.lang.Numbers + assert_non_null(numbersDesc); + auto addIt = numbersDesc->staticFns.find("add"); + assert_true(addIt != numbersDesc->staticFns.end()); + auto &addOverloads = addIt->second; + assert_int_equal(3, addOverloads.size()); + + // Overload 0: [:double :double] -> :double (intrinsic) + assert_int_equal(2, addOverloads[0].argTypes.size()); + assert_true(addOverloads[0].argTypes[0].contains(doubleType)); + assert_true(addOverloads[0].argTypes[1].contains(doubleType)); + assert_true(addOverloads[0].returnType.contains(doubleType)); + assert_int_equal((int)rt::CallType::Intrinsic, + (int)addOverloads[0].type); + assert_string_equal("FAdd", addOverloads[0].symbol.c_str()); + + // Overload 1: [:int :int] -> :int (intrinsic) + assert_int_equal(2, addOverloads[1].argTypes.size()); + assert_true(addOverloads[1].argTypes[0].contains(integerType)); + assert_true(addOverloads[1].argTypes[1].contains(integerType)); + assert_true(addOverloads[1].returnType.contains(integerType)); + assert_int_equal((int)rt::CallType::Intrinsic, + (int)addOverloads[1].type); + + // Overload 2: [:any :any] -> :any (call) + assert_int_equal(2, addOverloads[2].argTypes.size()); + assert_true(addOverloads[2].argTypes[0].isDynamic()); + assert_true(addOverloads[2].argTypes[1].isDynamic()); + assert_true(addOverloads[2].returnType.isDynamic()); + assert_int_equal((int)rt::CallType::Call, (int)addOverloads[2].type); + + // Verify clojure.lang.Numbers/gte (dynamic alias :bool) + auto gteIt = numbersDesc->staticFns.find("gte"); + assert_true(gteIt != numbersDesc->staticFns.end()); + assert_int_equal(3, gteIt->second.size()); + // Verify return type of first overload is booleanType (via :bool alias) + assert_true(gteIt->second[0].returnType.contains(booleanType)); + + // Verify java.lang.String + assert_non_null(stringDesc); + assert_int_equal(7, (int)stringDesc->type.determinedType()); + // Verify instance fns + auto containsIt = stringDesc->instanceFns.find("contains"); + assert_true(containsIt != stringDesc->instanceFns.end()); + assert_int_equal(1, containsIt->second.size()); + assert_string_equal("String_contains", + containsIt->second[0].symbol.c_str()); + assert_true(containsIt->second[0].returnType.contains(booleanType)); + + auto replaceIt = stringDesc->instanceFns.find("replace"); + assert_true(replaceIt != stringDesc->instanceFns.end()); + assert_int_equal(1, replaceIt->second.size()); + // Verify full symbol "java.lang.String" resolution + assert_true(replaceIt->second[0].argTypes[0].contains(stringType)); + assert_true(replaceIt->second[0].returnType.contains(stringType)); + + auto indexOfIt = stringDesc->instanceFns.find("indexOf"); + assert_true(indexOfIt != stringDesc->instanceFns.end()); + assert_int_equal(2, indexOfIt->second.size()); // Two overloads + + // indexOf(String) + assert_int_equal(1, indexOfIt->second[0].argTypes.size()); + assert_true(indexOfIt->second[0].argTypes[0].contains(stringType)); + + // indexOf(String, int) + assert_int_equal(2, indexOfIt->second[1].argTypes.size()); + assert_true(indexOfIt->second[1].argTypes[0].contains(stringType)); + assert_true(indexOfIt->second[1].argTypes[1].contains(integerType)); + + // Verify java.lang.Object/toString returns java.lang.String (full + // symbol) + assert_non_null(objDesc); + auto toStringIt = objDesc->instanceFns.find("toString"); + assert_true(toStringIt != objDesc->instanceFns.end()); + assert_true(toStringIt->second[0].returnType.contains(stringType)); + } catch (const rt::LanguageException &e) { cout << rt::getExceptionString(e) << endl; assert_true(false); @@ -176,17 +290,20 @@ static void test_class_aliasing_impl(void **state) { rootMap, Symbol_create(String_create("com.foo/B")), RT_boxPtr(classBMap)); - vector classes = buildClasses(RT_boxPtr(rootMap)); + // After assoc, rootMap owns a reference, but we must release our local + // ones. + + auto 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; + if (c->name == "com.foo/A") + descA = c.get(); + if (c->name == "com.foo/B") + descB = c.get(); } assert_non_null(descA); @@ -230,10 +347,10 @@ static void test_static_fields_impl(void **state) { rootMap, Symbol_create(String_create("com.foo/MyClass")), RT_boxPtr(classMap)); - vector classes = buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); assert_int_equal(1, classes.size()); - auto &c = classes[0]; + auto &c = *classes[0]; assert_int_equal(2, c.staticFields.size()); auto itVersion = c.staticFields.find("VERSION"); @@ -294,10 +411,10 @@ static void test_special_types_impl(void **state) { rootMap, Symbol_create(String_create("com.foo/A")), RT_boxPtr(classAMap)); - vector classes = buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); assert_int_equal(1, classes.size()); - auto &c = classes[0]; + auto &c = *classes[0]; auto it = c.instanceFns.find("f"); assert_true(it != c.instanceFns.end()); auto &intrinsic = it->second[0]; @@ -310,6 +427,91 @@ static void test_special_types_impl(void **state) { }); } +static void test_bridge_functions_impl(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + // {com.foo/Test {:instance-fields ["a" "b"], :static-fields {S1 10, S2 + // 20}}} + PersistentVector *ifields = PersistentVector_create(); + ifields = PersistentVector_conj(ifields, RT_boxPtr(String_create("a"))); + ifields = PersistentVector_conj(ifields, RT_boxPtr(String_create("b"))); + + PersistentArrayMap *sfields = PersistentArrayMap_empty(); + sfields = PersistentArrayMap_assoc( + sfields, Symbol_create(String_create("S1")), RT_boxInt32(10)); + sfields = PersistentArrayMap_assoc( + sfields, Symbol_create(String_create("S2")), RT_boxInt32(20)); + + PersistentArrayMap *classMap = PersistentArrayMap_empty(); + classMap = PersistentArrayMap_assoc( + classMap, Keyword_create(String_create("instance-fields")), + RT_boxPtr(ifields)); + classMap = PersistentArrayMap_assoc( + classMap, Keyword_create(String_create("static-fields")), + RT_boxPtr(sfields)); + + PersistentArrayMap *rootMap = PersistentArrayMap_empty(); + rootMap = PersistentArrayMap_assoc( + rootMap, Symbol_create(String_create("com.foo/Test")), + RT_boxPtr(classMap)); + + auto classes = buildClasses(RT_boxPtr(rootMap)); + assert_int_equal(1, classes.size()); + + ClassDescription &desc = *classes[0]; + void *ext = &desc; + + // Test instance field indices + RTValue fieldA = Symbol_create(String_create("a")); + RTValue fieldB = Symbol_create(String_create("b")); + RTValue fieldC = Symbol_create(String_create("c")); + + assert_int_equal(0, ClassExtension_fieldIndex(ext, fieldA)); + assert_int_equal(1, ClassExtension_fieldIndex(ext, fieldB)); + assert_int_equal(-1, ClassExtension_fieldIndex(ext, fieldC)); + + release(fieldA); + release(fieldB); + release(fieldC); + + // Test static field indices + RTValue sfield1 = Symbol_create(String_create("S1")); + RTValue sfield2 = Symbol_create(String_create("S2")); + + int32_t idx1 = ClassExtension_staticFieldIndex(ext, sfield1); + int32_t idx2 = ClassExtension_staticFieldIndex(ext, sfield2); + + assert_true(idx1 != -1); + assert_true(idx2 != -1); + assert_true(idx1 != idx2); + + // Test static field values + RTValue val1 = ClassExtension_getIndexedStaticField(ext, idx1); + RTValue val2 = ClassExtension_getIndexedStaticField(ext, idx2); + + // Values are retained by getIndexedStaticField + assert_int_equal(10, RT_unboxInt32(val1)); + assert_int_equal(20, RT_unboxInt32(val2)); + + release(val1); + release(val2); + + // Test setting static field + RTValue newVal = RT_boxInt32(100); + ClassExtension_setIndexedStaticField(ext, idx1, newVal); + RTValue checkVal = ClassExtension_getIndexedStaticField(ext, idx1); + assert_int_equal(100, RT_unboxInt32(checkVal)); + release(checkVal); + + release(sfield1); + release(sfield2); + }); +} + +static void test_bridge_functions(void **state) { + execute_test(test_bridge_functions_impl, state, "test_bridge_functions"); +} + static void test_special_types(void **state) { execute_test(test_special_types_impl, state, "test_special_types"); } @@ -345,7 +547,7 @@ static void test_class_key_not_symbol_impl(void **state) { PersistentArrayMap *rootMap = PersistentArrayMap_empty(); rootMap = PersistentArrayMap_assoc(rootMap, RT_boxInt32(1), RT_boxPtr(PersistentArrayMap_empty())); - buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); }); } @@ -360,7 +562,7 @@ static void test_class_value_not_map_impl(void **state) { PersistentArrayMap *rootMap = PersistentArrayMap_empty(); rootMap = PersistentArrayMap_assoc( rootMap, Symbol_create(String_create("A")), RT_boxInt32(1)); - buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); }); } @@ -379,7 +581,7 @@ static void test_object_type_not_int_impl(void **state) { PersistentArrayMap *rootMap = PersistentArrayMap_empty(); rootMap = PersistentArrayMap_assoc( rootMap, Symbol_create(String_create("A")), RT_boxPtr(classMap)); - buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); }); } @@ -398,7 +600,7 @@ static void test_alias_not_keyword_impl(void **state) { PersistentArrayMap *rootMap = PersistentArrayMap_empty(); rootMap = PersistentArrayMap_assoc( rootMap, Symbol_create(String_create("A")), RT_boxPtr(classMap)); - buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); }); } @@ -422,7 +624,7 @@ static void test_static_fields_not_map_impl(void **state) { PersistentArrayMap *rootMap = PersistentArrayMap_empty(); rootMap = PersistentArrayMap_assoc( rootMap, Symbol_create(String_create("A")), RT_boxPtr(classMap)); - buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); }); } @@ -444,7 +646,7 @@ static void test_instance_fns_not_map_impl(void **state) { PersistentArrayMap *rootMap = PersistentArrayMap_empty(); rootMap = PersistentArrayMap_assoc( rootMap, Symbol_create(String_create("A")), RT_boxPtr(classMap)); - buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); }); } @@ -471,7 +673,7 @@ static void test_static_field_key_not_symbol_impl(void **state) { PersistentArrayMap *rootMap = PersistentArrayMap_empty(); rootMap = PersistentArrayMap_assoc( rootMap, Symbol_create(String_create("A")), RT_boxPtr(classMap)); - buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); }); } @@ -496,7 +698,7 @@ static void test_intrinsic_key_not_symbol_impl(void **state) { PersistentArrayMap *rootMap = PersistentArrayMap_empty(); rootMap = PersistentArrayMap_assoc( rootMap, Symbol_create(String_create("A")), RT_boxPtr(classMap)); - buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); }); } @@ -521,7 +723,7 @@ static void test_intrinsic_value_not_vector_impl(void **state) { PersistentArrayMap *rootMap = PersistentArrayMap_empty(); rootMap = PersistentArrayMap_assoc( rootMap, Symbol_create(String_create("A")), RT_boxPtr(classMap)); - buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); }); } @@ -555,7 +757,7 @@ static void test_intrinsic_type_not_keyword_impl(void **state) { PersistentArrayMap *rootMap = PersistentArrayMap_empty(); rootMap = PersistentArrayMap_assoc( rootMap, Symbol_create(String_create("A")), RT_boxPtr(classMap)); - buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); }); } @@ -587,7 +789,7 @@ static void test_intrinsic_type_invalid_impl(void **state) { PersistentArrayMap *rootMap = PersistentArrayMap_empty(); rootMap = PersistentArrayMap_assoc( rootMap, Symbol_create(String_create("A")), RT_boxPtr(classMap)); - buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); }); } @@ -622,7 +824,7 @@ static void test_intrinsic_symbol_not_string_impl(void **state) { PersistentArrayMap *rootMap = PersistentArrayMap_empty(); rootMap = PersistentArrayMap_assoc( rootMap, Symbol_create(String_create("A")), RT_boxPtr(classMap)); - buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); }); } @@ -659,7 +861,7 @@ static void test_intrinsic_args_not_vector_impl(void **state) { PersistentArrayMap *rootMap = PersistentArrayMap_empty(); rootMap = PersistentArrayMap_assoc( rootMap, Symbol_create(String_create("A")), RT_boxPtr(classMap)); - buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); }); } @@ -701,7 +903,7 @@ static void test_unknown_arg_type_impl(void **state) { PersistentArrayMap *rootMap = PersistentArrayMap_empty(); rootMap = PersistentArrayMap_assoc( rootMap, Symbol_create(String_create("A")), RT_boxPtr(classMap)); - buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); }); } @@ -739,7 +941,7 @@ static void test_unknown_return_type_impl(void **state) { PersistentArrayMap *rootMap = PersistentArrayMap_empty(); rootMap = PersistentArrayMap_assoc( rootMap, Symbol_create(String_create("A")), RT_boxPtr(classMap)); - buildClasses(RT_boxPtr(rootMap)); + auto classes = buildClasses(RT_boxPtr(rootMap)); }); } @@ -748,6 +950,57 @@ static void test_unknown_return_type(void **state) { "test_unknown_return_type"); } +static void test_class_inheritance_linkage_impl(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + + // {Parent {:object-type 100}, Child {:extends Parent, :object-type 101}} + PersistentArrayMap *parentMap = PersistentArrayMap_empty(); + parentMap = PersistentArrayMap_assoc( + parentMap, Keyword_create(String_create("object-type")), + RT_boxInt32(100)); + + PersistentArrayMap *childMap = PersistentArrayMap_empty(); + childMap = PersistentArrayMap_assoc( + childMap, Keyword_create(String_create("extends")), + Symbol_create(String_create("Parent"))); + childMap = PersistentArrayMap_assoc( + childMap, Keyword_create(String_create("object-type")), + RT_boxInt32(101)); + + PersistentArrayMap *rootMap = PersistentArrayMap_empty(); + rootMap = PersistentArrayMap_assoc( + rootMap, Symbol_create(String_create("Parent")), RT_boxPtr(parentMap)); + rootMap = PersistentArrayMap_assoc( + rootMap, Symbol_create(String_create("Child")), RT_boxPtr(childMap)); + + compState.storeInternalClasses(RT_boxPtr(rootMap)); + + ::Class *parent = compState.classRegistry.getCurrent("Parent"); + ::Class *child = compState.classRegistry.getCurrent("Child"); + + assert_non_null(parent); + assert_non_null(child); + + // Check linkage (only in ClassDescription) + assert_int_equal(0, child->superclassCount); + assert_null(child->superclasses); + + rt::ClassDescription *childDesc = + static_cast(child->compilerExtension); + assert_ptr_equal(parent, childDesc->extends); + + Ptr_release(parent); + Ptr_release(child); + }); +} + +static void test_class_inheritance_linkage(void **state) { + execute_test(test_class_inheritance_linkage_impl, state, + "test_class_inheritance_linkage"); +} + int main(int argc, char **argv) { initialise_memory(); PersistentArrayMap_empty(); @@ -757,6 +1010,7 @@ int main(int argc, char **argv) { 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_bridge_functions), cmocka_unit_test(test_special_types), cmocka_unit_test(test_root_not_map), cmocka_unit_test(test_class_key_not_symbol), @@ -774,6 +1028,7 @@ int main(int argc, char **argv) { cmocka_unit_test(test_intrinsic_args_not_vector), cmocka_unit_test(test_unknown_arg_type), cmocka_unit_test(test_unknown_return_type), + cmocka_unit_test(test_class_inheritance_linkage), }; int result = cmocka_run_group_tests(tests, NULL, NULL); RuntimeInterface_cleanup(); diff --git a/backend-v2/tools/EdnParser.cpp b/backend-v2/tools/EdnParser.cpp index 4a64a7d8..db4b12b4 100644 --- a/backend-v2/tools/EdnParser.cpp +++ b/backend-v2/tools/EdnParser.cpp @@ -2,9 +2,16 @@ #include "RTValueWrapper.h" namespace rt { + +extern "C" void delete_class_description(void *ptr) { + delete static_cast(ptr); +} + ClassDescription::~ClassDescription() { - for (auto const &s : staticFields) { - release(s.second); + if (extends) + Ptr_release(extends); + for (auto v : staticFieldValues) { + release(v); } } @@ -94,6 +101,7 @@ TemporaryClassData::TemporaryClassData(RTValue from) { ClassDescription::ClassDescription(RTValue from, TemporaryClassData &classData) { ConsumedValue root(from); + this->extends = nullptr; if (getType(root.get()) != persistentArrayMapType) { throwInternalInconsistencyException( @@ -103,15 +111,51 @@ ClassDescription::ClassDescription(RTValue from, (PersistentArrayMap *)RT_unboxPtr(root.get()); retain(root.get()); - ConsumedValue typeWrapper(PersistentArrayMap_get( - description, Keyword_create(String_create("object-type")))); + ConsumedValue aliasWrapper(PersistentArrayMap_get( + description, Keyword_create(String_create("alias")))); - if (getType(typeWrapper.get()) == integerType) { - this->type = (objectType)RT_unboxInt32(typeWrapper.get()); - } else if (getType(typeWrapper.get()) == nilType) { - this->type = classType; + if (getType(aliasWrapper.get()) == keywordType) { + String *s = String_compactify(toString(aliasWrapper.take())); + string sAlias = String_c_str(s); + Ptr_release(s); + + if (sAlias == ":any") { + this->type = ObjectTypeSet::all(); + } else if (sAlias == ":nil") { + this->type = nilType; + } else { + // Fallback to integerType if it has one, or classType + retain(root.get()); + ConsumedValue typeWrapper(PersistentArrayMap_get( + description, Keyword_create(String_create("object-type")))); + if (getType(typeWrapper.get()) == integerType) { + this->type = (objectType)RT_unboxInt32(typeWrapper.get()); + } else { + this->type = classType; + } + } } else { - throwInternalInconsistencyException("Object-type is not an integer"); + retain(root.get()); + ConsumedValue typeWrapper(PersistentArrayMap_get( + description, Keyword_create(String_create("object-type")))); + + if (getType(typeWrapper.get()) == integerType) { + this->type = (objectType)RT_unboxInt32(typeWrapper.get()); + } else if (getType(typeWrapper.get()) == nilType) { + this->type = classType; + } else { + throwInternalInconsistencyException("Object-type is not an integer"); + } + } + + retain(root.get()); + ConsumedValue extendsWrapper(PersistentArrayMap_get( + description, Keyword_create(String_create("extends")))); + if (getType(extendsWrapper.get()) == symbolType || + getType(extendsWrapper.get()) == stringType) { + String *sParent = String_compactify(toString(extendsWrapper.take())); + this->parentName = String_c_str(sParent); + Ptr_release(sParent); } retain(root.get()); @@ -128,18 +172,49 @@ ClassDescription::ClassDescription(RTValue from, this->name = String_c_str(compactifiedName); Ptr_release(compactifiedName); - extends = nullptr; - retain(root.get()); ConsumedValue sfWrapper(PersistentArrayMap_get( description, Keyword_create(String_create("static-fields")))); if (getType(sfWrapper.get()) == persistentArrayMapType) { this->staticFields = parseStaticFields(sfWrapper.take()); + // Create stable indexing + for (auto const &[key, val] : this->staticFields) { + staticFieldIndices[key] = (int32_t)staticFieldValues.size(); + staticFieldValues.push_back(val); + } } else if (getType(sfWrapper.get()) != nilType) { throwInternalInconsistencyException(":static-fields must be a map."); } + retain(root.get()); + ConsumedValue ifieldsWrapper(PersistentArrayMap_get( + description, Keyword_create(String_create("instance-fields")))); + + if (getType(ifieldsWrapper.get()) == persistentVectorType) { + PersistentVector *vec = + (PersistentVector *)RT_unboxPtr(ifieldsWrapper.get()); + for (uword_t i = 0; i < vec->count; i++) { + retain(RT_boxPtr(vec)); // PersistentVector_nth consumes self + ConsumedValue fieldName(PersistentVector_nth(vec, i)); + // toString consumes. + String *ss = String_compactify(toString(fieldName.take())); + this->instanceFields[String_c_str(ss)] = (int32_t)i; + Ptr_release(ss); + } + } + + retain(root.get()); + ConsumedValue interfacesWrapper(PersistentArrayMap_get( + description, Keyword_create(String_create("interfaces")))); + + if (getType(interfacesWrapper.get()) == persistentVectorType) { + // These will be resolved later or stored as names. + // For now, let's assume classData can help or we store them as symbols. + // Actually, the previous implementation had ImplementedInterface** + // implementedInterfaces; which were resolved Classes. + } + retain(root.get()); ConsumedValue sfnsWrapper(PersistentArrayMap_get( description, Keyword_create(String_create("static-fns")))); @@ -162,6 +237,85 @@ ClassDescription::ClassDescription(RTValue from, } } +extern "C" { +int32_t ClassExtension_fieldIndex(void *ext, RTValue field) { + ClassDescription *description = (ClassDescription *)ext; + String *s = String_compactify(toString(field)); + string key = String_c_str(s); + Ptr_release(s); + + auto it = description->instanceFields.find(key); + if (it != description->instanceFields.end()) { + return it->second; + } + return -1; +} + +int32_t ClassExtension_staticFieldIndex(void *ext, RTValue staticField) { + ClassDescription *description = (ClassDescription *)ext; + String *s = String_compactify(toString(staticField)); + string key = String_c_str(s); + Ptr_release(s); + + auto it = description->staticFieldIndices.find(key); + if (it != description->staticFieldIndices.end()) { + return it->second; + } + return -1; +} + +RTValue ClassExtension_getIndexedStaticField(void *ext, int32_t i) { + ClassDescription *description = (ClassDescription *)ext; + if (i >= 0 && i < (int32_t)description->staticFieldValues.size()) { + RTValue val = description->staticFieldValues[i]; + retain(val); + return val; + } + return RT_boxNil(); +} + +RTValue ClassExtension_setIndexedStaticField(void *ext, int32_t i, + RTValue value) { + ClassDescription *description = (ClassDescription *)ext; + if (i >= 0 && i < (int32_t)description->staticFieldValues.size()) { + RTValue old = description->staticFieldValues[i]; + description->staticFieldValues[i] = value; + // Value is retained by the caller or we should retain it here? + // Class_setIndexedStaticField in C code did NOT retain 'value' but released + // 'oldValue'. RTValue oldValue = self->staticFields[i]; + // self->staticFields[i] = value; + // release(oldValue); + // return value; + release(old); + return value; + } + return value; +} + +ClojureFunction *ClassExtension_resolveInstanceCall(void *ext, RTValue name, + int32_t argCount) { + ClassDescription *description = (ClassDescription *)ext; + String *s = String_compactify(toString(name)); + string key = String_c_str(s); + Ptr_release(s); + + auto it = description->instanceFns.find(key); + if (it != description->instanceFns.end()) { + // FIXME: We need to resolve these to ClojureFunction* objects. + /* + for (auto const& intrinsic : it->second) { + // We need an actual ClojureFunction object here. + // This refactoring assumes that compile-time metadata can resolve to + // runtime functions. For now, if it's a :call type, we return the + // function. Wait, IntrinsicDescription only has the symbol name. The + // actual ClojureFunction should be built or cached. + } + */ + } + return nullptr; +} +} + unordered_map ClassDescription::parseStaticFields(RTValue from) { ConsumedValue root(from); @@ -176,7 +330,7 @@ ClassDescription::parseStaticFields(RTValue from) { if (getType(key) != symbolType) throwInternalInconsistencyException("Static fields key is not a symbol."); - // toString consumes. Protect key for the map. + // toString consumes. Protect borrowed key. retain(key); String *ss = String_compactify(toString(key)); string sKey = String_c_str(ss); @@ -232,7 +386,7 @@ ClassDescription::parseIntrinsics(RTValue from, TemporaryClassData &classData) { PersistentVector_iteratorNext(&it); } - // toString(key) consumes. Protect borrowed key for the map. + // toString consumes. Protect borrowed key for the map. retain(key); String *ss = String_compactify(toString(key)); string sKey = String_c_str(ss); @@ -320,7 +474,7 @@ IntrinsicDescription::IntrinsicDescription(RTValue from, if (sArgKey == ":this") { this->argTypes.push_back(ObjectTypeSet(classType)); } else if (sArgKey == ":any") { - this->argTypes.push_back(ObjectTypeSet::dynamicType()); + this->argTypes.push_back(ObjectTypeSet::all()); } else if (sArgKey == ":nil") { this->argTypes.push_back(ObjectTypeSet(nilType)); } else { @@ -359,7 +513,7 @@ IntrinsicDescription::IntrinsicDescription(RTValue from, Ptr_release(retStr); if (sRetKey == ":any") { - this->returnType = ObjectTypeSet::dynamicType(); + this->returnType = ObjectTypeSet::all(); } else if (sRetKey == ":nil") { this->returnType = ObjectTypeSet(nilType); } else if (classData.classesByName.find(sRetKey) != @@ -386,10 +540,10 @@ IntrinsicDescription::IntrinsicDescription(RTValue from, } #include -vector buildClasses(RTValue from) { +vector> buildClasses(RTValue from) { // from is consumed by TemporaryClassData TemporaryClassData classData(from); - vector retVal; + vector> retVal; unordered_set processed; @@ -399,7 +553,8 @@ vector buildClasses(RTValue from) { processed.insert(map); Ptr_retain(map); RTValue mapVal = RT_boxPtr(map); - retVal.push_back(ClassDescription(mapVal, classData)); + auto desc = make_unique(mapVal, classData); + retVal.push_back(std::move(desc)); } } diff --git a/backend-v2/tools/EdnParser.h b/backend-v2/tools/EdnParser.h index 29e8e983..2538ab7d 100644 --- a/backend-v2/tools/EdnParser.h +++ b/backend-v2/tools/EdnParser.h @@ -4,9 +4,12 @@ #include "../RuntimeHeaders.h" #include "../bridge/Exceptions.h" #include "../types/ObjectTypeSet.h" +#include #include using namespace std; +struct Class; + namespace rt { class TemporaryClassData { @@ -17,7 +20,7 @@ class TemporaryClassData { ~TemporaryClassData(); }; -enum class CallType { Call, Intrinsic }; +enum class CallType { Call, Intrinsic, ClojureFn }; class IntrinsicDescription { public: @@ -25,6 +28,7 @@ class IntrinsicDescription { CallType type; string symbol; ObjectTypeSet returnType; + IntrinsicDescription() = default; IntrinsicDescription(RTValue from, TemporaryClassData &classData); }; @@ -35,19 +39,41 @@ class ClassDescription { public: ObjectTypeSet type; - ClassDescription *extends; + ::Class *extends; string name; + string parentName; unordered_map staticFields; + vector staticFieldValues; + unordered_map staticFieldIndices; + + unordered_map instanceFields; + unordered_map> staticFns; unordered_map> instanceFns; + vector interfaces; + + ClassDescription() = default; ClassDescription(RTValue from, TemporaryClassData &classData); - ClassDescription(ClassDescription &&other) = default; - ClassDescription &operator=(ClassDescription &&other) = default; + ClassDescription(const ClassDescription &) = delete; + ClassDescription &operator=(const ClassDescription &) = delete; + ClassDescription(ClassDescription &&other) noexcept = default; + ClassDescription &operator=(ClassDescription &&other) noexcept = default; ~ClassDescription(); }; -vector buildClasses(RTValue from); +extern "C" { +void delete_class_description(void *p); +int32_t ClassExtension_fieldIndex(void *ext, RTValue field); +int32_t ClassExtension_staticFieldIndex(void *ext, RTValue staticField); +RTValue ClassExtension_getIndexedStaticField(void *ext, int32_t i); +RTValue ClassExtension_setIndexedStaticField(void *ext, int32_t i, + RTValue value); +ClojureFunction *ClassExtension_resolveInstanceCall(void *ext, RTValue name, + int32_t argCount); +} + +vector> buildClasses(RTValue from); } // namespace rt #endif diff --git a/backend-v2/tools/RTValueWrapper.h b/backend-v2/tools/RTValueWrapper.h index 836684d5..67988858 100644 --- a/backend-v2/tools/RTValueWrapper.h +++ b/backend-v2/tools/RTValueWrapper.h @@ -48,6 +48,45 @@ class ConsumedValue { operator RTValue() const { return value; } }; +/** + * RAII wrapper for Ptr (Object*) that ensures Ptr_release is called on + * destruction unless ownership is taken. + */ +template class PtrWrapper { + T *ptr; + bool consumed; + +public: + explicit PtrWrapper(T *p) : ptr(p), consumed(false) {} + + // Non-copyable + PtrWrapper(const PtrWrapper &) = delete; + PtrWrapper &operator=(const PtrWrapper &) = delete; + + // Moveable + PtrWrapper(PtrWrapper &&other) noexcept + : ptr(other.ptr), consumed(other.consumed) { + other.consumed = true; + } + + ~PtrWrapper() { + if (!consumed && ptr) { + Ptr_release(ptr); + } + } + + T *get() const { return ptr; } + + T *take() { + consumed = true; + return ptr; + } + + T *operator->() const { return ptr; } + operator T *() const { return ptr; } + operator bool() const { return ptr != nullptr; } +}; + } // namespace rt #endif diff --git a/backend-v2/types/ConstantBigInteger.h b/backend-v2/types/ConstantBigInteger.h index 418d8562..4b7b6391 100644 --- a/backend-v2/types/ConstantBigInteger.h +++ b/backend-v2/types/ConstantBigInteger.h @@ -2,6 +2,7 @@ #define CONSTANT_BIG_INTEGER_H #include +#include #include "ObjectTypeConstant.h" namespace rt { @@ -23,7 +24,12 @@ class ConstantBigInteger: public ObjectTypeConstant { mpz_clear(value); } virtual ObjectTypeConstant *copy() { return static_cast (new ConstantBigInteger(value)); } - virtual std::string toString() { return std::string(mpz_get_str(NULL, 10, value)); } + virtual std::string toString() { + char *str = mpz_get_str(NULL, 10, value); + std::string s(str); + free(str); + return s; + } virtual bool equals(ObjectTypeConstant *other) { if(ConstantBigInteger *i = dynamic_cast(other)) { return mpz_cmp(i->value, value) == 0; diff --git a/backend-v2/types/ConstantRatio.h b/backend-v2/types/ConstantRatio.h index f62ef2b6..22c0ec05 100644 --- a/backend-v2/types/ConstantRatio.h +++ b/backend-v2/types/ConstantRatio.h @@ -2,6 +2,7 @@ #define CONSTANT_RATIO_H #include +#include #include "ObjectTypeConstant.h" namespace rt { @@ -38,7 +39,12 @@ class ConstantRatio: public ObjectTypeConstant { mpq_clear(value); } virtual ObjectTypeConstant *copy() { return static_cast (new ConstantRatio(value)); } - virtual std::string toString() { return std::string(mpq_get_str(NULL, 10, value)); } + virtual std::string toString() { + char *str = mpq_get_str(NULL, 10, value); + std::string s(str); + free(str); + return s; + } virtual bool equals(ObjectTypeConstant *other) { if(ConstantRatio *i = dynamic_cast(other)) { return mpq_equal(i->value, value); diff --git a/backend-v2/types/ObjectTypeSet.h b/backend-v2/types/ObjectTypeSet.h index 5b3587fd..ec8f6879 100644 --- a/backend-v2/types/ObjectTypeSet.h +++ b/backend-v2/types/ObjectTypeSet.h @@ -1,79 +1,78 @@ #ifndef RT_OBJECT_TYPE_SET #define RT_OBJECT_TYPE_SET +#include "../runtime/ObjectProto.h" +#include "../runtime/defines.h" +#include #include #include -#include -#include "../runtime/defines.h" -#include "../runtime/ObjectProto.h" -#include +#include #include #include -#include +#include #include "ConstantBigInteger.h" -#include "ConstantInteger.h" #include "ConstantBool.h" +#include "ConstantClass.h" #include "ConstantDouble.h" #include "ConstantFunction.h" +#include "ConstantInteger.h" #include "ConstantKeyword.h" #include "ConstantNil.h" #include "ConstantRatio.h" #include "ConstantString.h" #include "ConstantSymbol.h" -#include "ConstantClass.h" +#include "bridge/Exceptions.h" namespace rt { class ObjectTypeSet { - private: +private: std::set internal; ObjectTypeConstant *constant = nullptr; bool isBoxed; - public: - ObjectTypeConstant * getConstant() const { - return constant; - } +public: + ObjectTypeConstant *getConstant() const { return constant; } - ObjectTypeSet(objectType type, bool isBoxed = false, ObjectTypeConstant *cons = nullptr) : constant(cons), isBoxed(isBoxed) { + ObjectTypeSet(objectType type, bool isBoxed = false, + ObjectTypeConstant *cons = nullptr) + : constant(cons), isBoxed(isBoxed) { internal.insert(type); - if(!constant && type == nilType) { - constant = static_cast(new ConstantNil()); - } + if (!constant && type == nilType) { + constant = static_cast(new ConstantNil()); + } } - + ObjectTypeSet() {} ~ObjectTypeSet() { - if(constant) delete constant; + if (constant) + delete constant; constant = nullptr; } - ObjectTypeSet(const ObjectTypeSet &other) : internal(other.internal), isBoxed(other.isBoxed) { - if(other.constant) constant = other.constant->copy(); - else constant = nullptr; + ObjectTypeSet(const ObjectTypeSet &other) + : internal(other.internal), isBoxed(other.isBoxed) { + if (other.constant) + constant = other.constant->copy(); + else + constant = nullptr; } - + std::set::const_iterator internalBegin() const { return internal.begin(); } - + std::set::const_iterator internalEnd() const { return internal.end(); } - - void insert(objectType type) { - internal.emplace(type); - } - - bool isEmpty() const { - return internal.size() == 0; - } - int size() const { - return internal.size(); - } + void insert(objectType type) { internal.emplace(type); } + + bool isEmpty() const { return internal.size() == 0; } + + int size() const { return internal.size(); } bool contains(objectType type) const { return internal.find(type) != internal.end(); @@ -87,24 +86,21 @@ class ObjectTypeSet { return contains(type) && internal.size() == 1 && !isBoxed; } - bool isDetermined() const { - return internal.size() == 1; - } - + bool isDetermined() const { return internal.size() == 1; } + bool isDynamic() const { - if(internal.size() > 1) { + if (internal.size() > 1) { return true; } return false; } - - bool isBoxedType() const { - return isBoxed; - } + + bool isBoxedType() const { return isBoxed; } ObjectTypeSet unboxed() const { ObjectTypeSet retVal = *this; - if(retVal.isDetermined()) retVal.isBoxed = false; + if (retVal.isDetermined()) + retVal.isBoxed = false; return retVal; } @@ -114,64 +110,72 @@ class ObjectTypeSet { return retVal; } - bool isUnboxedPointer() const { return !isScalar() && !isBoxedScalar() && !isBoxed; } bool isBoxedPointer() const { return isDetermined() && !isScalar() && !isBoxedScalar() && isBoxed; - } - + } + bool isScalar() const { - if(isBoxed) return false; - if(!isDetermined()) return false; - switch(determinedType()) { - case integerType: - case booleanType: - case doubleType: - case nilType: - case keywordType: - case symbolType: - return true; + if (isBoxed) + return false; + if (!isDetermined()) + return false; + switch (determinedType()) { + case integerType: + case booleanType: + case doubleType: + case nilType: + case keywordType: + case symbolType: + return true; default: return false; } } bool isBoxedScalar() const { - if(!isBoxed) return false; - if(!isDetermined()) return false; - switch(determinedType()) { - case integerType: - case booleanType: - case doubleType: - case nilType: - case keywordType: - case symbolType: - return true; + if (!isBoxed) + return false; + if (!isDetermined()) + return false; + switch (determinedType()) { + case integerType: + case booleanType: + case doubleType: + case nilType: + case keywordType: + case symbolType: + return true; default: return false; } } objectType determinedType() const { - assert(isDetermined() && "Type not determined"); + if (!isDetermined()) { + throwInternalInconsistencyException("Type not determined"); + } return *(internal.begin()); } void remove(objectType type) { auto pos = internal.find(type); - if (pos == internal.end()) return; + if (pos == internal.end()) + return; internal.erase(pos); } - + ObjectTypeSet expansion(const ObjectTypeSet &other) const { /* Expansion removes all constants */ auto retVal = ObjectTypeSet(*this); retVal.internal.insert(other.internal.begin(), other.internal.end()); - retVal.isBoxed = (isBoxed && !isEmpty()) || (other.isBoxed && !other.isEmpty()) || retVal.size() > 1; - if(retVal.constant) delete retVal.constant; + retVal.isBoxed = (isBoxed && !isEmpty()) || + (other.isBoxed && !other.isEmpty()) || retVal.size() > 1; + if (retVal.constant) + delete retVal.constant; retVal.constant = nullptr; return retVal; } @@ -180,17 +184,17 @@ class ObjectTypeSet { /* Expansion removes all constants */ auto retVal = ObjectTypeSet(); retVal.internal = internal; - retVal.isBoxed = isBoxed; + retVal.isBoxed = isBoxed; return retVal; } - ObjectTypeSet restriction(const ObjectTypeSet &other) const { /* Restriction preserves constant type for this */ auto retVal = ObjectTypeSet(); - std::set_intersection(internal.begin(), internal.end(), - other.internal.begin(), other.internal.end(), - std::inserter(retVal.internal, retVal.internal.begin())); + std::set_intersection( + internal.begin(), internal.end(), other.internal.begin(), + other.internal.end(), + std::inserter(retVal.internal, retVal.internal.begin())); retVal.isBoxed = isBoxed; if (constant != nullptr && retVal.contains(constant->constantType)) { retVal.constant = constant->copy(); @@ -200,27 +204,29 @@ class ObjectTypeSet { return retVal; } - ObjectTypeSet& operator=(const ObjectTypeSet &other) { + ObjectTypeSet &operator=(const ObjectTypeSet &other) { isBoxed = other.isBoxed; internal = other.internal; - if(constant) delete constant; + if (constant) + delete constant; constant = nullptr; - if(other.constant) constant = other.constant->copy(); + if (other.constant) + constant = other.constant->copy(); return *this; } - friend bool operator==(const ObjectTypeSet &first, const ObjectTypeSet &second); - friend bool operator<(const ObjectTypeSet &first, const ObjectTypeSet &second); + friend bool operator==(const ObjectTypeSet &first, + const ObjectTypeSet &second); + friend bool operator<(const ObjectTypeSet &first, + const ObjectTypeSet &second); static ObjectTypeSet dynamicType() { auto all = ObjectTypeSet::all(); all.isBoxed = true; return all; } - - static ObjectTypeSet empty() { - return ObjectTypeSet(); - } + + static ObjectTypeSet empty() { return ObjectTypeSet(); } static ObjectTypeSet all() { ObjectTypeSet retVal; @@ -241,18 +247,22 @@ class ObjectTypeSet { retVal.isBoxed = true; return retVal; } - + static ObjectTypeSet fromVector(std::vector types) { - assert((types.size() > 1) && "ObjectTypeSet::fromVector requires vector of length at least 2"); + assert((types.size() > 1) && + "ObjectTypeSet::fromVector requires vector of length at least 2"); ObjectTypeSet retVal; - for (auto t: types) retVal.insert(t); + for (auto t : types) + retVal.insert(t); retVal.isBoxed = true; return retVal; } - + static std::string typeStringForArg(const ObjectTypeSet &arg) { - if(!arg.isDetermined()) return "LO"; - else switch (arg.determinedType()) { + if (!arg.isDetermined()) + return "LO"; + else + switch (arg.determinedType()) { case integerType: return (arg.isBoxed ? "LJ" : "J"); case stringType: @@ -287,25 +297,89 @@ class ObjectTypeSet { return "LA"; } } - + static std::string typeStringForArgs(const std::vector &args) { std::stringstream retval; - for (auto i: args) retval << typeStringForArg(i); + for (auto i : args) + retval << typeStringForArg(i); return retval.str(); } - static std::string fullyQualifiedMethodKey(const std::string &name, const std::vector &args, const ObjectTypeSet &retVal) { - return name + "_" + typeStringForArgs(args) + "_" + typeStringForArg(retVal); + static std::string + fullyQualifiedMethodKey(const std::string &name, + const std::vector &args, + const ObjectTypeSet &retVal) { + return name + "_" + typeStringForArgs(args) + "_" + + typeStringForArg(retVal); } - static std::string recursiveMethodKey(const std::string &name, const std::vector &args) { + static std::string + recursiveMethodKey(const std::string &name, + const std::vector &args) { return name + "_" + typeStringForArgs(args); - } + } + + static std::string toHumanReadableName(objectType type) { + switch (type) { + case integerType: + return "Int"; + case stringType: + return "String"; + case persistentListType: + return "List"; + case persistentVectorType: + return "Vector"; + case doubleType: + return "Double"; + case nilType: + return "Nil"; + case booleanType: + return "Boolean"; + case symbolType: + return "Symbol"; + case keywordType: + return "Keyword"; + case functionType: + return "Function"; + case bigIntegerType: + return "BigInt"; + case ratioType: + return "Ratio"; + case classType: + return "Class"; + case persistentArrayMapType: + return "Map"; + case persistentVectorNodeType: + return "VectorNode"; + case concurrentHashMapType: + return "CHM"; + default: + return "Unknown"; + } + } std::string toString() const { - std::string retVal = typeStringForArg(*this); - if(constant) retVal += " constant value: " + constant->toString() + " of " + std::to_string(constant->constantType); - return retVal; + std::stringstream ss; + if (isBoxed) + ss << "Boxed("; + if (internal.empty()) { + ss << "Empty"; + } else if (internal.size() == 1) { + ss << toHumanReadableName(*internal.begin()); + if (constant) { + ss << "=" << constant->toString(); + } + } else { + ss << "("; + for (auto it = internal.begin(); it != internal.end(); ++it) { + ss << toHumanReadableName(*it) + << (std::next(it) == internal.end() ? "" : "|"); + } + ss << ")"; + } + if (isBoxed) + ss << ")"; + return ss.str(); } }; diff --git a/frontend/resources/rt-classes.edn b/frontend/resources/rt-classes.edn index 80ea09a9..aeb4c66c 100644 --- a/frontend/resources/rt-classes.edn +++ b/frontend/resources/rt-classes.edn @@ -28,10 +28,12 @@ clojure.lang.BigInt {:extends java.lang.Object + :alias :bigint :object-type 13} clojure.lang.Ratio {:extends java.lang.Object + :alias :ratio :object-type 14} clojure.lang.Boolean @@ -58,22 +60,134 @@ {:static-fns {add [{:args [:double :double] :type :intrinsic :symbol "FAdd" :returns :double} {:args [:int :int] :type :intrinsic :symbol "Add" :returns :int} - {:args [:any :any] :type :call :symbol "add" :returns :any}] + {:args [:int :double] :type :intrinsic :symbol "Add_ID" :returns :double} + {:args [:double :int] :type :intrinsic :symbol "Add_DI" :returns :double} + {:args [:int :bigint] :type :intrinsic :symbol "Add_IB" :returns :bigint} + {:args [:bigint :int] :type :intrinsic :symbol "Add_BI" :returns :bigint} + {:args [:bigint :double] :type :intrinsic :symbol "Add_BD" :returns :double} + {:args [:double :bigint] :type :intrinsic :symbol "Add_DB" :returns :double} + {:args [:ratio :double] :type :intrinsic :symbol "Add_RD" :returns :double} + {:args [:double :ratio] :type :intrinsic :symbol "Add_DR" :returns :double} + {:args [:bigint :ratio] :type :intrinsic :symbol "Add_BR" :returns :any} + {:args [:ratio :bigint] :type :intrinsic :symbol "Add_RB" :returns :any} + {:args [:int :ratio] :type :intrinsic :symbol "Add_IR" :returns :any} + {:args [:ratio :int] :type :intrinsic :symbol "Add_RI" :returns :any} + {:args [:bigint :bigint] :type :call :symbol "BigInteger_add" :returns :bigint} + {:args [:ratio :ratio] :type :call :symbol "Ratio_add" :returns :any} + {:args [:any :any] :type :call :symbol "Numbers_add" :returns :any}] minus [{:args [:double :double] :type :intrinsic :symbol "FSub" :returns :double} {:args [:int :int] :type :intrinsic :symbol "Sub" :returns :int} - {:args [:any :any] :type :call :symbol "sub" :returns :any}] + {:args [:int :double] :type :intrinsic :symbol "Sub_ID" :returns :double} + {:args [:double :int] :type :intrinsic :symbol "Sub_DI" :returns :double} + {:args [:int :bigint] :type :intrinsic :symbol "Sub_IB" :returns :bigint} + {:args [:bigint :int] :type :intrinsic :symbol "Sub_BI" :returns :bigint} + {:args [:bigint :double] :type :intrinsic :symbol "Sub_BD" :returns :double} + {:args [:double :bigint] :type :intrinsic :symbol "Sub_DB" :returns :double} + {:args [:ratio :double] :type :intrinsic :symbol "Sub_RD" :returns :double} + {:args [:double :ratio] :type :intrinsic :symbol "Sub_DR" :returns :double} + {:args [:bigint :ratio] :type :intrinsic :symbol "Sub_BR" :returns :any} + {:args [:ratio :bigint] :type :intrinsic :symbol "Sub_RB" :returns :any} + {:args [:int :ratio] :type :intrinsic :symbol "Sub_IR" :returns :any} + {:args [:ratio :int] :type :intrinsic :symbol "Sub_RI" :returns :any} + {:args [:bigint :bigint] :type :call :symbol "BigInteger_sub" :returns :bigint} + {:args [:ratio :ratio] :type :call :symbol "Ratio_sub" :returns :any} + {:args [:any :any] :type :call :symbol "Numbers_sub" :returns :any}] multiply [{:args [:double :double] :type :intrinsic :symbol "FMul" :returns :double} {:args [:int :int] :type :intrinsic :symbol "Mul" :returns :int} - {:args [:any :any] :type :call :symbol "mul" :returns :any}] - divide [{:args [:double :double] :type :intrinsic :symbol "FDiv" :returns :double} - {:args [:int :int] :type :intrinsic :symbol "Div" :returns :int} - {:args [:any :any] :type :call :symbol "div" :returns :any}] + {:args [:int :double] :type :intrinsic :symbol "Mul_ID" :returns :double} + {:args [:double :int] :type :intrinsic :symbol "Mul_DI" :returns :double} + {:args [:int :bigint] :type :intrinsic :symbol "Mul_IB" :returns :bigint} + {:args [:bigint :int] :type :intrinsic :symbol "Mul_BI" :returns :bigint} + {:args [:bigint :double] :type :intrinsic :symbol "Mul_BD" :returns :double} + {:args [:double :bigint] :type :intrinsic :symbol "Mul_DB" :returns :double} + {:args [:ratio :double] :type :intrinsic :symbol "Mul_RD" :returns :double} + {:args [:double :ratio] :type :intrinsic :symbol "Mul_DR" :returns :double} + {:args [:bigint :ratio] :type :intrinsic :symbol "Mul_BR" :returns :any} + {:args [:ratio :bigint] :type :intrinsic :symbol "Mul_RB" :returns :any} + {:args [:int :ratio] :type :intrinsic :symbol "Mul_IR" :returns :any} + {:args [:ratio :int] :type :intrinsic :symbol "Mul_RI" :returns :any} + {:args [:bigint :bigint] :type :call :symbol "BigInteger_mul" :returns :bigint} + {:args [:ratio :ratio] :type :call :symbol "Ratio_mul" :returns :any} + {:args [:any :any] :type :call :symbol "Numbers_mul" :returns :any}] + divide [{:args [:double :double] :type :intrinsic :symbol "FDiv" :returns :double} + {:args [:int :int] :type :call :symbol "Integer_div" :returns :any} + {:args [:int :double] :type :intrinsic :symbol "Div_ID" :returns :double} + {:args [:double :int] :type :intrinsic :symbol "Div_DI" :returns :double} + {:args [:bigint :double] :type :intrinsic :symbol "Div_BD" :returns :double} + {:args [:double :bigint] :type :intrinsic :symbol "Div_DB" :returns :double} + {:args [:ratio :double] :type :intrinsic :symbol "Div_RD" :returns :double} + {:args [:double :ratio] :type :intrinsic :symbol "Div_DR" :returns :double} + {:args [:bigint :ratio] :type :intrinsic :symbol "Div_BR" :returns :any} + {:args [:ratio :bigint] :type :intrinsic :symbol "Div_RB" :returns :any} + {:args [:int :ratio] :type :intrinsic :symbol "Div_IR" :returns :any} + {:args [:ratio :int] :type :intrinsic :symbol "Div_RI" :returns :any} + {:args [:bigint :bigint] :type :call :symbol "BigInteger_div" :returns :any} + {:args [:ratio :ratio] :type :call :symbol "Ratio_div" :returns :any} + {:args [:any :any] :type :call :symbol "Numbers_div" :returns :any}] gte [{:args [:double :double] :type :intrinsic :symbol "FCmpOGE" :returns :bool} {:args [:int :int] :type :intrinsic :symbol "ICmpSGE" :returns :bool} + {:args [:int :double] :type :intrinsic :symbol "ICmpSGE_ID" :returns :bool} + {:args [:double :int] :type :intrinsic :symbol "ICmpSGE_DI" :returns :bool} + {:args [:bigint :double] :type :intrinsic :symbol "FCmpOGE_BD" :returns :bool} + {:args [:double :bigint] :type :intrinsic :symbol "FCmpOGE_DB" :returns :bool} + {:args [:ratio :double] :type :intrinsic :symbol "FCmpOGE_RD" :returns :bool} + {:args [:double :ratio] :type :intrinsic :symbol "FCmpOGE_DR" :returns :bool} + {:args [:bigint :ratio] :type :intrinsic :symbol "FCmpOGE_BR" :returns :bool} + {:args [:ratio :bigint] :type :intrinsic :symbol "FCmpOGE_RB" :returns :bool} + {:args [:int :ratio] :type :intrinsic :symbol "FCmpOGE_IR" :returns :bool} + {:args [:ratio :int] :type :intrinsic :symbol "FCmpOGE_RI" :returns :bool} + {:args [:bigint :bigint] :type :call :symbol "BigInteger_gte" :returns :bool} + {:args [:ratio :ratio] :type :call :symbol "Ratio_gte" :returns :bool} {:args [:any :any] :type :call :symbol "gte" :returns :bool}] - lt [{:args [:double :double] :type :intrinsic :symbol "FCmpOLT" :returns :bool} - {:args [:int :int] :type :intrinsic :symbol "ICmpOLT" :returns :bool} - {:args [:any :any] :type :call :symbol "lt" :returns :bool}]}} + + + gt [{:args [:double :double] :type :intrinsic :symbol "FCmpOGT" :returns :bool} + {:args [:int :int] :type :intrinsic :symbol "ICmpSGT" :returns :bool} + {:args [:int :double] :type :intrinsic :symbol "ICmpSGT_ID" :returns :bool} + {:args [:double :int] :type :intrinsic :symbol "ICmpSGT_DI" :returns :bool} + {:args [:bigint :double] :type :intrinsic :symbol "FCmpOGT_BD" :returns :bool} + {:args [:double :bigint] :type :intrinsic :symbol "FCmpOGT_DB" :returns :bool} + {:args [:ratio :double] :type :intrinsic :symbol "FCmpOGT_RD" :returns :bool} + {:args [:double :ratio] :type :intrinsic :symbol "FCmpOGT_DR" :returns :bool} + {:args [:bigint :ratio] :type :intrinsic :symbol "FCmpOGT_BR" :returns :bool} + {:args [:ratio :bigint] :type :intrinsic :symbol "FCmpOGT_RB" :returns :bool} + {:args [:int :ratio] :type :intrinsic :symbol "FCmpOGT_IR" :returns :bool} + {:args [:ratio :int] :type :intrinsic :symbol "FCmpOGT_RI" :returns :bool} + {:args [:bigint :bigint] :type :call :symbol "BigInteger_gt" :returns :bool} + {:args [:ratio :ratio] :type :call :symbol "Ratio_gt" :returns :bool} + {:args [:any :any] :type :call :symbol "gt" :returns :bool}] + lt [{:args [:double :double] :type :intrinsic :symbol "FCmpOLT" :returns :bool} + {:args [:int :int] :type :intrinsic :symbol "ICmpSLT" :returns :bool} + {:args [:int :double] :type :intrinsic :symbol "ICmpSLT_ID" :returns :bool} + {:args [:double :int] :type :intrinsic :symbol "ICmpSLT_DI" :returns :bool} + {:args [:bigint :double] :type :intrinsic :symbol "FCmpOLT_BD" :returns :bool} + {:args [:double :bigint] :type :intrinsic :symbol "FCmpOLT_DB" :returns :bool} + {:args [:ratio :double] :type :intrinsic :symbol "FCmpOLT_RD" :returns :bool} + {:args [:double :ratio] :type :intrinsic :symbol "FCmpOLT_DR" :returns :bool} + {:args [:bigint :ratio] :type :intrinsic :symbol "FCmpOLT_BR" :returns :bool} + {:args [:ratio :bigint] :type :intrinsic :symbol "FCmpOLT_RB" :returns :bool} + {:args [:int :ratio] :type :intrinsic :symbol "FCmpOLT_IR" :returns :bool} + {:args [:ratio :int] :type :intrinsic :symbol "FCmpOLT_RI" :returns :bool} + {:args [:bigint :bigint] :type :call :symbol "BigInteger_lt" :returns :bool} + {:args [:ratio :ratio] :type :call :symbol "Ratio_lt" :returns :bool} + {:args [:any :any] :type :call :symbol "lt" :returns :bool}] + lte [{:args [:double :double] :type :intrinsic :symbol "FCmpOLE" :returns :bool} + {:args [:int :int] :type :intrinsic :symbol "ICmpSLE" :returns :bool} + {:args [:int :double] :type :intrinsic :symbol "ICmpSLE_ID" :returns :bool} + {:args [:double :int] :type :intrinsic :symbol "ICmpSLE_DI" :returns :bool} + {:args [:bigint :double] :type :intrinsic :symbol "FCmpOLE_BD" :returns :bool} + {:args [:double :bigint] :type :intrinsic :symbol "FCmpOLE_DB" :returns :bool} + {:args [:ratio :double] :type :intrinsic :symbol "FCmpOLE_RD" :returns :bool} + {:args [:double :ratio] :type :intrinsic :symbol "FCmpOLE_DR" :returns :bool} + {:args [:bigint :ratio] :type :intrinsic :symbol "FCmpOLE_BR" :returns :bool} + {:args [:ratio :bigint] :type :intrinsic :symbol "FCmpOLE_RB" :returns :bool} + {:args [:int :ratio] :type :intrinsic :symbol "FCmpOLE_IR" :returns :bool} + {:args [:ratio :int] :type :intrinsic :symbol "FCmpOLE_RI" :returns :bool} + {:args [:bigint :bigint] :type :call :symbol "BigInteger_lte" :returns :bool} + {:args [:ratio :ratio] :type :call :symbol "Ratio_lte" :returns :bool} + {:args [:any :any] :type :call :symbol "lte" :returns :bool}]}} + + java.lang.Math {:static-fns @@ -111,9 +225,19 @@ clojure.lang.Util {:static-fns - {equiv [{:args [:int :int] :type :intrinsic :symbol "ICmpEQ" :returns :bool} - {:args [:double :double] :type :intrinsic :symbol "FCmpOEQ" :returns :bool} - {:args [:any :any] :type :call :symbol "equals" :returns :bool}]}} + {equiv [{:args [:int :int] :type :intrinsic :symbol "ICmpEQ" :returns :bool} + {:args [:double :double] :type :intrinsic :symbol "FCmpOEQ" :returns :bool} + {:args [:bigint :double] :type :intrinsic :symbol "FCmpOEQ_BD" :returns :bool} + {:args [:bigint :int] :type :call :symbol "BigInteger_equalsInt" :returns :bool} + {:args [:int :bigint] :type :call :symbol "BigInteger_intEquals" :returns :bool} + {:args [:double :bigint] :type :intrinsic :symbol "FCmpOEQ_DB" :returns :bool} + {:args [:ratio :double] :type :intrinsic :symbol "FCmpOEQ_RD" :returns :bool} + {:args [:double :ratio] :type :intrinsic :symbol "FCmpOEQ_DR" :returns :bool} + {:args [:int :double] :type :intrinsic :symbol "FCmpOEQ_ID" :returns :bool} + {:args [:double :int] :type :intrinsic :symbol "FCmpOEQ_DI" :returns :bool} + {:args [:any :any] :type :call :symbol "equals_managed" :returns :bool}]}} + + clojure.lang.PersistentVector {:object-type 9 diff --git a/frontend/src/clojure/rt/core.clj b/frontend/src/clojure/rt/core.clj index 6f22b658..13f20593 100644 --- a/frontend/src/clojure/rt/core.clj +++ b/frontend/src/clojure/rt/core.clj @@ -113,7 +113,8 @@ (defn analyze ([s filename] (analyze s filename false false)) ([s filename trivial-tree? simple-tree?] - (with-bindings {#'a/run-passes run-passes} + (with-bindings {#'a/run-passes run-passes + #'*file* filename} (with-redefs [ana/parse-quote quote/parse-quote a/parse-deftype* deftype/parse-deftype* annotate-host-info deftype/annotate-host-info @@ -121,7 +122,7 @@ validate-interfaces (fn [_])] (let [reader (t/source-logging-push-back-reader s 1 filename)] (loop [form (r/read {:eof :eof} reader) - current-env (a/empty-env) + current-env (assoc (a/empty-env) :file filename) ret-val []] (if (= :eof form) (do @@ -158,10 +159,10 @@ (defn -main ([infile] (let [parts (split infile #"\.")] - (compile (slurp "resources/rt-protocols.edn") "../backend-v2/rt-protocols.cljb" infile) - (compile (slurp "resources/rt-classes.edn") "../backend-v2/rt-classes.cljb" infile) + (compile (slurp "resources/rt-protocols.edn") "../backend-v2/rt-protocols.cljb" "rt-protocols.edn") + (compile (slurp "resources/rt-classes.edn") "../backend-v2/rt-classes.cljb" "rt-classes.edn") (compile (slurp infile) (str (join "." (butlast parts)) ".cljb") infile - false true) + false false) )) ([] (println "Generating protobuf definitions into bytecode.proto file. To compile use file name as parameter") (generate-protobuf-defs)))