diff --git a/backend-v2/bridge/ClassLookup.cpp b/backend-v2/bridge/ClassLookup.cpp index a9eda4d4..f8bdfb0e 100644 --- a/backend-v2/bridge/ClassLookup.cpp +++ b/backend-v2/bridge/ClassLookup.cpp @@ -14,7 +14,10 @@ ClassLookupByName(const char *className, void *jitEngine) { JITEngine *engine = static_cast(jitEngine); Class *cls = engine->threadsafeState.classRegistry.getCurrent(className); if (!cls) { - throwInternalInconsistencyException("ClassLookup: class not found: " + + cls = engine->threadsafeState.protocolRegistry.getCurrent(className); + } + if (!cls) { + throwInternalInconsistencyException("ClassLookup: class/protocol not found: " + std::string(className)); } return cls; @@ -27,6 +30,9 @@ ClassLookupByRegisterId(int32_t registerId, void *jitEngine) { } JITEngine *engine = static_cast(jitEngine); Class *cls = engine->threadsafeState.classRegistry.getCurrent(registerId); + if (!cls) { + cls = engine->threadsafeState.protocolRegistry.getCurrent(registerId); + } if (!cls) { return nullptr; } diff --git a/backend-v2/bridge/Exceptions.cpp b/backend-v2/bridge/Exceptions.cpp index 160a9aa1..ff76309c 100644 --- a/backend-v2/bridge/Exceptions.cpp +++ b/backend-v2/bridge/Exceptions.cpp @@ -71,8 +71,8 @@ static std::mutex gSymbolizerMutex; void registerJitFunction( uword_t address, uword_t size, const char *name, const void *objectData, - size_t objectSize, - std::map locationToForm) { + size_t objectSize, std::map locationToForm, + std::map locationToContextForm) { std::lock_guard lock(gJitMapMutex); JitFunctionEntry entry; entry.size = size; @@ -83,18 +83,19 @@ void registerJitFunction( objectSize); } entry.locationToForm = std::move(locationToForm); + entry.locationToContextForm = std::move(locationToContextForm); gJitFunctions[address] = std::move(entry); } void registerJitFunction(uword_t address, uword_t size, const char *name, const void *objectData, size_t objectSize) { - registerJitFunction(address, size, name, objectData, objectSize, {}); + registerJitFunction(address, size, name, objectData, objectSize, {}, {}); } extern "C" void registerJitFunction_C(uword_t address, uword_t size, const char *name, const void *objectData, size_t objectSize) { - registerJitFunction(address, size, name, objectData, objectSize, {}); + registerJitFunction(address, size, name, objectData, objectSize, {}, {}); } static bool isInfrastructureFrame(const std::string &name, @@ -460,15 +461,22 @@ LanguageException::LanguageException(const std::string &name, RTValue message, for (uint32_t i = 0; i < inlinedInfo.getNumberOfFrames(); ++i) { auto &info = inlinedInfo.getFrame(i); if (info.FileName != "") { - SourceLocation loc; - loc.file = info.FileName; - loc.line = info.Line; - loc.column = info.Column; - auto formIt = entry.locationToForm.find(loc); - - if (formIt == entry.locationToForm.end() && - !entry.locationToForm.empty()) { - // Try a fuzzy match based on basename and line + SourceLocation exactLoc(info.FileName, info.Line, info.Column, info.Discriminator); + BaseSourceLocation baseLoc(exactLoc); + // Try to find exact form + auto exactIt = entry.locationToForm.find(exactLoc); + if (exactIt != entry.locationToForm.end()) { + this->exactForm = exactIt->second; + } + + // Try to find context form + auto contextIt = entry.locationToContextForm.find(baseLoc); + if (contextIt != entry.locationToContextForm.end()) { + this->form = contextIt->second; + } + + // If either is missing, try fuzzy matching for BOTH + if (this->exactForm.empty() || this->form.empty()) { std::string base = info.FileName; size_t lastS = base.find_last_of('/'); if (lastS != std::string::npos) @@ -480,23 +488,35 @@ LanguageException::LanguageException(const std::string &name, RTValue message, if (kLastS != std::string::npos) kBase = kBase.substr(kLastS + 1); - // If line is 0, we take the first match for the file - if (kBase == base && - (k.line == (int)info.Line || info.Line == 0)) { - this->form = v; - std::stringstream locSs; - locSs << info.FileName << ":" << k.line << ":" - << k.column; - this->sourceLocation = locSs.str(); - foundForm = true; - break; + if (kBase == base && k.line == (int)info.Line) { + if (this->exactForm.empty() && + k.column == (int)info.Column && + k.discriminator == (int)info.Discriminator) { + this->exactForm = v; + } + } + } + + for (auto const &[k, v] : entry.locationToContextForm) { + std::string kBase = k.file; + size_t kLastS = kBase.find_last_of('/'); + if (kLastS != std::string::npos) + kBase = kBase.substr(kLastS + 1); + + if (kBase == base && k.line == (int)info.Line && k.column == (int)info.Column) { + if (this->form.empty()) { + this->form = v; + } } } - } else if (formIt != entry.locationToForm.end()) { - this->form = formIt->second; + } + + if (!this->form.empty() || !this->exactForm.empty()) { std::stringstream locSs; locSs << info.FileName << ":" << info.Line << ":" << info.Column; + if (info.Discriminator > 0) + locSs << " (disc " << info.Discriminator << ")"; this->sourceLocation = locSs.str(); foundForm = true; break; @@ -520,9 +540,10 @@ LanguageException::LanguageException(const std::string &name, RTValue message, LanguageException::LanguageException(const std::string &name, RTValue message, RTValue payload, const std::string &form, + const std::string &exactForm, const std::string &sourceLocation) : name(name), message(message), payload(payload), form(form), - sourceLocation(sourceLocation) { + exactForm(exactForm), sourceLocation(sourceLocation) { capturedStack = captureCurrentStack(); } @@ -612,6 +633,13 @@ LanguageException::toString(llvm::symbolize::LLVMSymbolizer &symbolizer, << c(Colors::RESET, useColor) << "\n"; } + if (!exactForm.empty() && exactForm != form) { + ss << "\n " << c(Colors::BOLD, useColor) + << "Exact expression:" << c(Colors::RESET, useColor) << "\n"; + ss << " " << Colors::CODE(useColor) << exactForm + << c(Colors::RESET, useColor) << "\n"; + } + ss << "\n " << c(Colors::BOLD, useColor) << "Stack Trace:" << c(Colors::RESET, useColor) << "\n"; symbolizeStackChain(ss, capturedStack, symbolizer, moduleName, slide, mode, @@ -759,7 +787,7 @@ void throwCodeGenerationException(const std::string &errorMessage, throw rt::LanguageException( "CodeGenerationException", RT_boxPtr(::String_createDynamicStr(retval.str().c_str())), RT_boxNil(), - form, locSs.str()); + form, form, locSs.str()); } void throwCodeGenerationException( diff --git a/backend-v2/bridge/Exceptions.h b/backend-v2/bridge/Exceptions.h index 3164827b..bd640fb0 100644 --- a/backend-v2/bridge/Exceptions.h +++ b/backend-v2/bridge/Exceptions.h @@ -35,6 +35,7 @@ struct JitFunctionEntry { std::string name; std::vector objectData; std::map locationToForm; + std::map locationToContextForm; }; struct CapturedStack { @@ -52,6 +53,7 @@ class LanguageException : public std::exception { RTValue message; RTValue payload; std::string form; + std::string exactForm; std::string sourceLocation; std::shared_ptr capturedStack; mutable std::string cachedMessage; @@ -60,7 +62,8 @@ class LanguageException : public std::exception { public: LanguageException(const std::string &name, RTValue message, RTValue payload); LanguageException(const std::string &name, RTValue message, RTValue payload, - const std::string &form, const std::string &sourceLocation); + const std::string &form, const std::string &exactForm, + const std::string &sourceLocation); LanguageException(const LanguageException &other); LanguageException &operator=(const LanguageException &other); ~LanguageException() noexcept override; @@ -77,6 +80,7 @@ class LanguageException : public std::exception { RTValue getMessage() const { return message; } RTValue getPayload() const { return payload; } const std::string &getForm() const { return form; } + const std::string &getExactForm() const { return exactForm; } const std::string &getSourceLocation() const { return sourceLocation; } std::shared_ptr getCapturedStack() const { return capturedStack; @@ -100,7 +104,8 @@ throwCodeGenerationException(const std::string &errorMessage, // New registration function with form mapping void registerJitFunction( uword_t addr, size_t size, const char *name, const void *objData, - size_t objSize, std::map locationToForm); + size_t objSize, std::map locationToForm, + std::map locationToContextForm); } // namespace rt diff --git a/backend-v2/bridge/InstanceCallStub.cpp b/backend-v2/bridge/InstanceCallStub.cpp index ff5a76f5..50c038a6 100644 --- a/backend-v2/bridge/InstanceCallStub.cpp +++ b/backend-v2/bridge/InstanceCallStub.cpp @@ -7,12 +7,6 @@ using namespace rt; -void releaseInstanceStubArgs(int32_t argCount, RTValue *args) { - for (int i = 0; i < argCount + 1; i++) { - release(args[i]); - } -} - /** * The "Slow Path Bouncer" for dynamic instance calls. * @@ -23,114 +17,116 @@ void releaseInstanceStubArgs(int32_t argCount, RTValue *args) { * method/type. * 3. Updates the Inline Cache (IC) slot with the new bridge address and type * key. - * 4. Executes the bridge and returns the result. + * 4. Returns the bridge address. */ -// Slow path has peculiar semantics. It does not consume when successful -// but it does when it throws. +// Slow path has simple semantics: it NEVER consumes arguments. This ensures that +// the caller (JIT or C++ test) can safely reuse or release them. +extern "C" _Atomic(uword_t) globalMethodICEpoch; + extern "C" __attribute__((visibility("default"))) void * InstanceCallSlowPath(void *slot, const char *methodName, int32_t argCount, RTValue *args, uint64_t boxedMask, void *jitEngine) { - try { - if (!jitEngine) { - throwInternalInconsistencyException( - "InstanceCallSlowPath: jitEngine is null"); - } - if (!slot) { - throwInternalInconsistencyException( - "InstanceCallSlowPath: IC slot is null"); - } - if (!methodName) { - throwInternalInconsistencyException( - "InstanceCallSlowPath: methodName is null"); - } + if (!jitEngine) { + throwInternalInconsistencyException( + "InstanceCallSlowPath: jitEngine is null"); + } + if (!slot) { + throwInternalInconsistencyException( + "InstanceCallSlowPath: IC slot is null"); + } + if (!methodName) { + throwInternalInconsistencyException( + "InstanceCallSlowPath: methodName is null"); + } - JITEngine *engine = static_cast(jitEngine); - InlineCache *ic = static_cast(slot); + JITEngine *engine = static_cast(jitEngine); + InlineCache *ic = static_cast(slot); - // 1. Determine the actual type of the instance at runtime - RTValue instance = args[0]; - objectType instanceType = getType(instance); + // 1. Determine the actual type of the instance at runtime + RTValue instance = args[0]; + objectType instanceType = getType(instance); - // 2. Prepare the argument types for JIT specialization - std::vector argTypes; - for (int i = 0; i < argCount; i++) { - // bit i of boxedMask corresponds to args[i+1] (the i-th method argument) - bool isBoxed = i < 20 ? (boxedMask >> i) & 1 : true; - if (isBoxed) { - argTypes.push_back(ObjectTypeSet::dynamicType()); - } else { - // For unboxed arguments, we specialize to the actual runtime type - argTypes.push_back(ObjectTypeSet(getType(args[i + 1]))); - } + // 2. Prepare the argument types for JIT specialization + std::vector argTypes; + for (int i = 0; i < argCount; i++) { + // bit i of boxedMask corresponds to args[i+1] (the i-th method argument) + bool isBoxed = i < 20 ? (boxedMask >> i) & 1 : true; + if (isBoxed) { + argTypes.push_back(ObjectTypeSet::dynamicType()); + } else { + // For unboxed arguments, we specialize to the actual runtime type + argTypes.push_back(ObjectTypeSet(getType(args[i + 1]))); } + } - // 3. Trigger JIT compilation of a specialized bridge stub - auto future = engine->compileInstanceCallBridge( - methodName, ObjectTypeSet(instanceType), argTypes, slot); + // 3. Trigger JIT compilation of a specialized bridge stub + auto future = engine->compileInstanceCallBridge( + methodName, ObjectTypeSet(instanceType), argTypes, slot); - /* TODO - The bridge, when compiled, should never have Ebr_flush_critical - * inside of it. This is easily achievable because for now it only ever - * calls to runtime functions which should always be smooth and fast. If - * there was a flush inside, Ebr could allow the old bridge code to be - * released while execution still happens in runtime which then - * would want to return to the non existing bridge. - * - * A way forward, if we ever wanted flushes in methods, could be to - * construct another bridge with a signature exactly equal to the top-level - * bridge that would only convert parameters/return value. Then we can force - * a tail call in the bridge that can be reclaimed so that it disappears - * from stack. - */ + /* TODO - The bridge, when compiled, should never have Ebr_flush_critical + * inside of it. This is easily achievable because for now it only ever + * calls to runtime functions which should always be smooth and fast. If + * there was a flush inside, Ebr could allow the old bridge code to be + * released while execution still happens in runtime which then + * would want to return to the non existing bridge. + * + * A way forward, if we ever wanted flushes in methods, could be to + * construct another bridge with a signature exactly equal to the top-level + * bridge that would only convert parameters/return value. Then we can force + * a tail call in the bridge that can be reclaimed so that it disappears + * from stack. + */ - // std::cout << future.get().optimizedIR << std::endl; + // std::cout << future.get().optimizedIR << std::endl; - // Block until the JIT compilation is finished and get the executable - // address - llvm::orc::ExecutorAddr bridgeAddr = future.get().address; - void *bridgePtr = bridgeAddr.toPtr(); - // 4. Update the Inline Cache for subsequent calls (atomically) - // Double check if another thread already updated it for our type - InlineCache currentIC; - if constexpr (K_WORD_SIZE == 8) { + // Block until the JIT compilation is finished and get the executable + // address + llvm::orc::ExecutorAddr bridgeAddr = future.get().address; + void *bridgePtr = bridgeAddr.toPtr(); + // 4. Update the Inline Cache for subsequent calls (atomically) + // Double check if another thread already updated it for our type + InlineCache currentIC; + if constexpr (K_WORD_SIZE == 8) { #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Watomic-alignment" #endif - // 16-byte atomic load. We have alignas(16) on InlineCache and ensure - // alignment in the JIT. On x86_64, this requires cmpxchg16b. - __atomic_load((unsigned __int128 *)ic, (unsigned __int128 *)¤tIC, - __ATOMIC_ACQUIRE); + // 16-byte atomic load. We have alignas(16) on InlineCache and ensure + // alignment in the JIT. On x86_64, this requires cmpxchg16b. + __atomic_load((unsigned __int128 *)ic, (unsigned __int128 *)¤tIC, + __ATOMIC_ACQUIRE); #if defined(__clang__) #pragma clang diagnostic pop #endif - } else { - __atomic_load((uint64_t *)ic, (uint64_t *)¤tIC, __ATOMIC_ACQUIRE); - } + } else { + __atomic_load((uint64_t *)ic, (uint64_t *)¤tIC, __ATOMIC_ACQUIRE); + } - if (currentIC.key == (word_t)instanceType) { - return currentIC.value; - } + uword_t currentEpochValue = + atomic_load_explicit(&globalMethodICEpoch, memory_order_acquire); + word_t expectedKey = + (word_t)((currentEpochValue << 32) | (word_t)instanceType); + + if (currentIC.key == expectedKey) { + return currentIC.value; + } - InlineCache newCache = {(word_t)instanceType, bridgePtr}; - if constexpr (K_WORD_SIZE == 8) { + InlineCache newCache = {expectedKey, bridgePtr}; + if constexpr (K_WORD_SIZE == 8) { #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Watomic-alignment" #endif - __atomic_store((unsigned __int128 *)ic, (unsigned __int128 *)&newCache, - __ATOMIC_RELEASE); + __atomic_store((unsigned __int128 *)ic, (unsigned __int128 *)&newCache, + __ATOMIC_RELEASE); #if defined(__clang__) #pragma clang diagnostic pop #endif - } else { - __atomic_store((uint64_t *)ic, (uint64_t *)&newCache, __ATOMIC_RELEASE); - } - - // 5. Return the bridge pointer. The caller will jump to it. - return bridgePtr; - } catch (...) { - releaseInstanceStubArgs(argCount, args); - throw; + } else { + __atomic_store((uint64_t *)ic, (uint64_t *)&newCache, __ATOMIC_RELEASE); } + + // 5. Return the bridge pointer. The caller will jump to it. + return bridgePtr; } \ No newline at end of file diff --git a/backend-v2/bridge/SourceLocation.h b/backend-v2/bridge/SourceLocation.h index 2ff1bc1a..129e8cad 100644 --- a/backend-v2/bridge/SourceLocation.h +++ b/backend-v2/bridge/SourceLocation.h @@ -9,8 +9,36 @@ struct SourceLocation { std::string file; int line; int column; + int discriminator; + + SourceLocation() : line(0), column(0), discriminator(0) {} + SourceLocation(std::string f, int l, int c, int d = 0) + : file(f), line(l), column(c), discriminator(d) {} bool operator<(const SourceLocation &other) const { + if (file != other.file) return file < other.file; + if (line != other.line) return line < other.line; + if (column != other.column) return column < other.column; + return discriminator < other.discriminator; + } + + bool operator==(const SourceLocation &other) const { + return file == other.file && line == other.line && column == other.column && + discriminator == other.discriminator; + } +}; + +struct BaseSourceLocation { + std::string file; + int line; + int column; + + BaseSourceLocation(const SourceLocation &loc) + : file(loc.file), line(loc.line), column(loc.column) {} + BaseSourceLocation(std::string f, int l, int c) + : file(f), line(l), column(c) {} + + bool operator<(const BaseSourceLocation &other) const { if (file != other.file) return file < other.file; if (line != other.line) return line < other.line; return column < other.column; diff --git a/backend-v2/codegen/CodeGen.cpp b/backend-v2/codegen/CodeGen.cpp index 1b57ed8d..f287bcc6 100644 --- a/backend-v2/codegen/CodeGen.cpp +++ b/backend-v2/codegen/CodeGen.cpp @@ -29,7 +29,7 @@ CodeGenResult CodeGen::release() && { generatedConstants.clear(); // Ensure destructor doesn't re-release auto icSlotNames = std::move(invokeManager.getICSlotNames()); return {std::move(TSContext), std::move(TheModule), std::move(constants), - std::move(icSlotNames), std::move(formMap)}; + std::move(icSlotNames), std::move(formMap), std::move(contextFormMap)}; } std::string CodeGen::codegenTopLevel(const Node &node) { @@ -158,8 +158,8 @@ std::string CodeGen::generateInstanceCallBridge( // 5. Generate the specialized instance call // This will emit direct calls or a dispatch tree based on the provided // types. - TypedValue result = - invokeManager.generateInstanceCall(methodName, instanceTV, callArgs); + TypedValue result = invokeManager.generateDeterminedInstanceCall( + methodName, instanceTV, callArgs); // 6. Ensure result is boxed and return Builder.CreateRet(valueEncoder.box(result).value); @@ -372,18 +372,27 @@ TypedValue CodeGen::codegen(const Node &node, MemoryManagement::UnwindGuidanceGuard guidanceGuard(memoryManagement, &node.unwindmemory()); + DebugLocGuard locGuard(Builder); + auto env = node.env(); // Record form for exception reporting - SourceLocation loc; - loc.file = env.file(); - loc.line = env.line(); - loc.column = env.column(); + int currentID = nodeIDCounter++; + SourceLocation loc(env.file(), env.line(), env.column(), currentID); + BaseSourceLocation baseLoc(loc); + formMap[loc] = node.form(); + if (contextFormMap.find(baseLoc) == contextFormMap.end()) { + contextFormMap[baseLoc] = node.form(); + } if (!LexicalBlocks.empty()) { - Builder.SetCurrentDebugLocation(llvm::DILocation::get( - TheContext, env.line(), env.column(), LexicalBlocks.back())); + const auto *debugLoc = llvm::DILocation::get( + TheContext, env.line(), env.column(), LexicalBlocks.back()); + if (currentID > 0) { + debugLoc = debugLoc->cloneWithDiscriminator(currentID); + } + Builder.SetCurrentDebugLocation(debugLoc); } if (!functionMetricsStack.empty()) { diff --git a/backend-v2/codegen/CodeGen.h b/backend-v2/codegen/CodeGen.h index a0396a5d..10180dd1 100644 --- a/backend-v2/codegen/CodeGen.h +++ b/backend-v2/codegen/CodeGen.h @@ -38,6 +38,7 @@ struct CodeGenResult { std::vector constants; std::vector icSlotNames; std::map formMap; + std::map contextFormMap; }; struct FunctionMetrics { @@ -114,6 +115,10 @@ class CodeGen { return formMap; } + const std::map &getContextFormMap() const { + return contextFormMap; + } + std::string codegenTopLevel(const Node &node); std::string generateInstanceCallBridge(const std::string &moduleName, @@ -262,8 +267,20 @@ class CodeGen { } }; + class DebugLocGuard { + llvm::IRBuilder<> &Builder; + llvm::DebugLoc savedLoc; + + public: + DebugLocGuard(llvm::IRBuilder<> &Builder) + : Builder(Builder), savedLoc(Builder.getCurrentDebugLocation()) {} + ~DebugLocGuard() { Builder.SetCurrentDebugLocation(savedLoc); } + }; + private: std::map formMap; + std::map contextFormMap; + int nodeIDCounter = 0; public: std::string suggestedFunctionName; diff --git a/backend-v2/codegen/invoke/InvokeManager.h b/backend-v2/codegen/invoke/InvokeManager.h index 6da94fed..8d59840f 100644 --- a/backend-v2/codegen/invoke/InvokeManager.h +++ b/backend-v2/codegen/invoke/InvokeManager.h @@ -50,11 +50,6 @@ class InvokeManager { std::unordered_map typeIntrinsics; std::vector icSlotNames; - TypedValue generateDeterminedInstanceCall( - const std::string &methodName, TypedValue instance, - const std::vector &args, CleanupChainGuard *guard = nullptr, - const clojure::rt::protobuf::bytecode::Node *node = nullptr); - TypedValue generateDynamicInstanceCall( const std::string &methodName, TypedValue instance, const std::vector &args, CleanupChainGuard *guard = nullptr, @@ -99,6 +94,11 @@ class InvokeManager { const std::vector &args, CleanupChainGuard *guard = nullptr); + TypedValue generateDeterminedInstanceCall( + const std::string &methodName, TypedValue instance, + const std::vector &args, CleanupChainGuard *guard = nullptr, + const clojure::rt::protobuf::bytecode::Node *node = nullptr); + TypedValue generateInstanceCall( const std::string &methodName, TypedValue instance, const std::vector &args, CleanupChainGuard *guard = nullptr, diff --git a/backend-v2/codegen/invoke/InvokeManager_InstanceCall.cpp b/backend-v2/codegen/invoke/InvokeManager_InstanceCall.cpp index 1205ccdc..7e0af7c5 100644 --- a/backend-v2/codegen/invoke/InvokeManager_InstanceCall.cpp +++ b/backend-v2/codegen/invoke/InvokeManager_InstanceCall.cpp @@ -12,18 +12,24 @@ using namespace llvm; using namespace std; +extern "C" _Atomic(uword_t) globalMethodICEpoch; + namespace rt { TypedValue InvokeManager::generateInstanceCall( const std::string &methodName, TypedValue instance, const std::vector &args, CleanupChainGuard *guard, const clojure::rt::protobuf::bytecode::Node *node) { - if (!instance.type.isDetermined()) { - return generateDynamicInstanceCall(methodName, instance, args, guard, node); - } else { - return generateDeterminedInstanceCall(methodName, instance, args, guard, - node); - } + /* Instance call is always dynamic because protocols can change at any time so + * we always need IC. Once we implement T2 we will add the static path again + */ + + // if (!instance.type.isDetermined()) { + return generateDynamicInstanceCall(methodName, instance, args, guard, node); + // } else { + // return generateDeterminedInstanceCall(methodName, instance, args, guard, + // node); + // } } TypedValue InvokeManager::generateDynamicInstanceCall( @@ -108,7 +114,19 @@ TypedValue InvokeManager::generateDynamicInstanceCall( BasicBlock *slowPath = BasicBlock::Create(TheContext, "ic_miss", currentFn); BasicBlock *callBB = BasicBlock::Create(TheContext, "ic_call", currentFn); - Value *isHit = builder.CreateICmpEQ(cachedTag, currentTypeIdent); + // Load current epoch + Value *epochPtr = + ConstantInt::get(types.i64Ty, (uintptr_t)&globalMethodICEpoch); + epochPtr = builder.CreateIntToPtr(epochPtr, types.ptrTy); + Value *currentEpoch = + builder.CreateLoad(types.wordTy, epochPtr, "current_epoch"); + + // Combine epoch and type: (epoch << 32) | type + Value *shiftedEpoch = builder.CreateShl(currentEpoch, 32, "shifted_epoch"); + Value *currentKey = + builder.CreateOr(shiftedEpoch, currentTypeIdent, "current_ic_key"); + + Value *isHit = builder.CreateICmpEQ(cachedTag, currentKey); builder.CreateCondBr(isHit, fastPath, slowPath); // --- SLOW PATH: Call Bouncer --- @@ -220,12 +238,24 @@ TypedValue InvokeManager::generateDeterminedInstanceCall( Ptr_retain(curr_cls); while (curr_ext) { + // 1. Search in classs own methods auto it = curr_ext->instanceFns.find(methodName); if (it != curr_ext->instanceFns.end()) { versions_ptr = &it->second; break; } + // 2. Search in implemented protocols + for (auto const &[protoName, protoMethods] : curr_ext->implements) { + auto itP = protoMethods.find(methodName); + if (itP != protoMethods.end()) { + versions_ptr = &itP->second; + break; + } + } + if (versions_ptr) + break; + if (curr_ext->parentName.empty() || curr_ext->parentName == curr_ext->name) { break; @@ -492,12 +522,24 @@ InvokeManager::predictInstanceCallType(const std::string &methodName, Ptr_retain(curr_cls); while (curr_ext) { + // 1. Search in classs own methods auto it = curr_ext->instanceFns.find(methodName); if (it != curr_ext->instanceFns.end()) { versions_ptr = &it->second; break; } + // 2. Search in implemented protocols + for (auto const &[protoName, protoMethods] : curr_ext->implements) { + auto itP = protoMethods.find(methodName); + if (itP != protoMethods.end()) { + versions_ptr = &itP->second; + break; + } + } + if (versions_ptr) + break; + if (curr_ext->parentName.empty() || curr_ext->parentName == curr_ext->name) { break; diff --git a/backend-v2/codegen/ops/HostInteropNode.cpp b/backend-v2/codegen/ops/HostInteropNode.cpp index f728da27..fc3933df 100644 --- a/backend-v2/codegen/ops/HostInteropNode.cpp +++ b/backend-v2/codegen/ops/HostInteropNode.cpp @@ -7,6 +7,7 @@ #include "bridge/Exceptions.h" #include "bytecode.pb.h" #include "codegen/TypedValue.h" +#include "types/ObjectTypeSet.h" #include using namespace std; @@ -31,13 +32,18 @@ TypedValue CodeGen::codegen(const Node &node, const HostInteropNode &subnode, ObjectTypeSet CodeGen::getType(const Node &node, const HostInteropNode &subnode, const ObjectTypeSet &typeRestrictions) { - if (subnode.isassignable()) { - return ObjectTypeSet::all(); - } - - auto targetType = getType(subnode.target(), ObjectTypeSet::all()); - return this->invokeManager.predictInstanceCallType(subnode.morf(), targetType, - {}); + /* Instance call is always dynamic because protocols can change at any time so + * we always need IC. Once we implement T2 we will add the static path again + */ + return ObjectTypeSet::dynamicType(); + // if (subnode.isassignable()) { + // return ObjectTypeSet::all(); + // } + + // auto targetType = getType(subnode.target(), ObjectTypeSet::all()); + // return this->invokeManager.predictInstanceCallType(subnode.morf(), + // targetType, + // {}); } } // namespace rt \ No newline at end of file diff --git a/backend-v2/codegen/ops/InstanceCallNode.cpp b/backend-v2/codegen/ops/InstanceCallNode.cpp index ca3caffb..f3dd3d65 100644 --- a/backend-v2/codegen/ops/InstanceCallNode.cpp +++ b/backend-v2/codegen/ops/InstanceCallNode.cpp @@ -16,7 +16,7 @@ using namespace clojure::rt::protobuf::bytecode; namespace rt { /* The node currently supports only the simplest case - no inheritance, no - * interfaces. TODO: implement full support for inheritance and interfaces. + * protocols. TODO: implement full support for inheritance and protocols. */ TypedValue CodeGen::codegen(const Node &node, const InstanceCallNode &subnode, const ObjectTypeSet &typeRestrictions) { @@ -41,14 +41,19 @@ TypedValue CodeGen::codegen(const Node &node, const InstanceCallNode &subnode, ObjectTypeSet CodeGen::getType(const Node &node, const InstanceCallNode &subnode, const ObjectTypeSet &typeRestrictions) { - auto instanceType = getType(subnode.instance(), ObjectTypeSet::all()); - std::vector args; - for (int i = 0; i < subnode.args_size(); i++) { - args.push_back(getType(subnode.args(i), ObjectTypeSet::all()).unboxed()); - } - - return this->invokeManager.predictInstanceCallType(subnode.method(), - instanceType, args); + return ObjectTypeSet::dynamicType(); + /* Instance call is always dynamic because protocols can change at any time so + * we always need IC. Once we implement T2 we will add the static path again + */ + + // auto instanceType = getType(subnode.instance(), ObjectTypeSet::all()); + // std::vector args; + // for (int i = 0; i < subnode.args_size(); i++) { + // args.push_back(getType(subnode.args(i), ObjectTypeSet::all()).unboxed()); + // } + + // return this->invokeManager.predictInstanceCallType(subnode.method(), + // instanceType, args); } } // namespace rt \ No newline at end of file diff --git a/backend-v2/codegen/ops/IsInstanceNode.cpp b/backend-v2/codegen/ops/IsInstanceNode.cpp index 2377de54..57586ad5 100644 --- a/backend-v2/codegen/ops/IsInstanceNode.cpp +++ b/backend-v2/codegen/ops/IsInstanceNode.cpp @@ -30,6 +30,8 @@ TypedValue CodeGen::codegen(const Node &node, const IsInstanceNode &subnode, string className = subnode.class_(); if (className.find("class ") == 0) { className = className.substr(6); + } else if (className.find("interface ") == 0) { + className = className.substr(10); } Value *classNameGlobal = @@ -56,11 +58,19 @@ ObjectTypeSet CodeGen::getType(const Node &node, const IsInstanceNode &subnode, string className = subnode.class_(); if (className.find("class ") == 0) { className = className.substr(6); + } else if (className.find("interface ") == 0) { + className = className.substr(10); } - ScopedRef<::Class> cls( - this->compilerState.classRegistry.getCurrent(className.c_str())); + ::Class *clsRaw = + this->compilerState.classRegistry.getCurrent(className.c_str()); + if (!clsRaw) { + clsRaw = this->compilerState.protocolRegistry.getCurrent(className.c_str()); + } + ScopedRef<::Class> cls(clsRaw); + if (!cls) { - throwCodeGenerationException("Class " + className + " not found", node); + throwCodeGenerationException("Class/Protocol " + className + " not found", + node); } ScopedRef<::Class> target(this->compilerState.classRegistry.getCurrent( @@ -71,9 +81,13 @@ ObjectTypeSet CodeGen::getType(const Node &node, const IsInstanceNode &subnode, "' not found", node); } - return ObjectTypeSet( - booleanType, false, - new ConstantBoolean(Class_isInstance(cls.get(), target.get()))); + bool val = Class_isInstance(cls.get(), target.get()); + if (!val) { + /* It is possible to become a member during runtime, but one can never stop + * being a member */ + return ObjectTypeSet(booleanType); + } + return ObjectTypeSet(booleanType, false, new ConstantBoolean(true)); } } // namespace rt diff --git a/backend-v2/jit/JITEngine.cpp b/backend-v2/jit/JITEngine.cpp index 79fa72c1..d8273a15 100644 --- a/backend-v2/jit/JITEngine.cpp +++ b/backend-v2/jit/JITEngine.cpp @@ -204,6 +204,7 @@ JITEngine::compileGeneric(std::function codegenFunc, auto module = std::move(result.module); auto constants = std::move(result.constants); auto formMap = std::move(result.formMap); + auto contextFormMap = std::move(result.contextFormMap); this->optimize(*module, fName); @@ -276,7 +277,8 @@ JITEngine::compileGeneric(std::function codegenFunc, registerJitFunction( Sym->getValue(), captured.buffer->getBufferSize(), fName.c_str(), captured.buffer->getBufferStart(), - captured.buffer->getBufferSize(), std::move(formMap)); + captured.buffer->getBufferSize(), std::move(formMap), + std::move(contextFormMap)); this->capturedObjectBuffers.erase( this->capturedObjectBuffers.begin() + foundIdx); } else { @@ -287,7 +289,8 @@ JITEngine::compileGeneric(std::function codegenFunc, // the object (though for JIT it usually is). registerJitFunction(Sym->getValue(), 1024 * 1024, fName.c_str(), nullptr, 0, - std::move(formMap)); + std::move(formMap), + std::move(contextFormMap)); } } diff --git a/backend-v2/runtime/Class.c b/backend-v2/runtime/Class.c index 331ebc32..28f42274 100644 --- a/backend-v2/runtime/Class.c +++ b/backend-v2/runtime/Class.c @@ -1,18 +1,22 @@ +#include "BridgedObject.h" +#include "Ebr.h" +#include "RTValue.h" #include "Class.h" #include "Hash.h" #include "Object.h" #include "word.h" #include +#include Class *ClassLookupByName(const char *className, void *jitEngine); Class *ClassLookupByRegisterId(int32_t registerId, void *jitEngine); -Class *Class_createInterface(String *name, String *className, - int32_t extendsInterfaceCount, - Class **extendsInterfaces) { +Class *Class_createProtocol(String *name, String *className, + int32_t extendsProtocolCount, + Class **extendsProtocols) { Class *retVal = - Class_create(name, className, extendsInterfaceCount, extendsInterfaces); - retVal->isInterface = true; + Class_create(name, className, extendsProtocolCount, extendsProtocols); + retVal->isProtocol = true; return retVal; } @@ -20,12 +24,23 @@ Class *Class_create(String *name, String *className, int32_t superclassCount, Class **superclasses) { Class *self = allocate(sizeof(Class)); - self->isInterface = false; + self->isProtocol = false; self->registerId = 0; // unregistered self->name = name; self->className = className; - self->superclassCount = superclassCount; - self->superclasses = superclasses; + + ClassList *list = allocate(sizeof(ClassList) + sizeof(Class *) * superclassCount); + list->count = superclassCount; + for (int32_t i = 0; i < superclassCount; i++) { + list->classes[i] = superclasses[i]; + // superclasses[i] was already retained or owned by the caller's expectation + } + atomic_init(&self->superclasses, list); + if (superclasses) { + deallocate(superclasses); + } + + atomic_init(&self->implementedProtocols, NULL); self->compilerExtension = NULL; self->compilerExtensionDestructor = NULL; @@ -56,13 +71,20 @@ String *Class_toString(Class *self) { void Class_destroy(Class *self) { Ptr_release(self->name); Ptr_release(self->className); - if (self->superclasses) { - for (int32_t i = 0; i < self->superclassCount; i++) - Ptr_release(self->superclasses[i]); + ClassList *supers = atomic_load_explicit(&self->superclasses, memory_order_relaxed); + if (supers) { + for (int32_t i = 0; i < supers->count; i++) + Ptr_release(supers->classes[i]); + deallocate(supers); } - if (self->superclasses) - deallocate(self->superclasses); + ClassList *list = atomic_load_explicit(&self->implementedProtocols, memory_order_relaxed); + if (list) { + for (int32_t i = 0; i < list->count; i++) { + Ptr_release(list->classes[i]); + } + deallocate(list); + } if (self->compilerExtension && self->compilerExtensionDestructor) { self->compilerExtensionDestructor(self->compilerExtension); @@ -84,17 +106,74 @@ bool Class_isInstanceClassName(const char *className, void *jitEngine, } /* outside refcount system */ -/* TODO: This is not yet done for interfaces */ +/* TODO: This is not yet done for protocols */ bool Class_isInstance(Class *current, Class *target) { assert(target && "Target must not be null"); assert(current && "Current must not be null"); if (current->registerId == target->registerId || current->registerId == objectRootType) { return true; } - for (int32_t i = 0; i < target->superclassCount; i++) { - if (Class_isInstance(current, target->superclasses[i])) { - return true; + ClassList *supers = atomic_load_explicit(&target->superclasses, memory_order_acquire); + if (supers) { + for (int32_t i = 0; i < supers->count; i++) { + if (Class_isInstance(current, supers->classes[i])) { + return true; + } + } + } + ClassList *list = atomic_load_explicit(&target->implementedProtocols, memory_order_acquire); + if (list) { + for (int32_t i = 0; i < list->count; i++) { + if (Class_isInstance(current, list->classes[i])) { + return true; + } } } return false; } + +static void reclaim_class_list_destructor(void *contents, void *jit) { + ClassList *list = (ClassList *)contents; + for (int32_t i = 0; i < list->count; i++) { + Ptr_release(list->classes[i]); + } + deallocate(list); +} + +void Class_addProtocol(Class *self, Protocol *proto) { + Ptr_retain(proto); + while (true) { + ClassList *oldList = + atomic_load_explicit(&self->implementedProtocols, memory_order_acquire); + + int32_t oldCount = oldList ? oldList->count : 0; + int32_t newCount = oldCount + 1; + ClassList *newList = + allocate(sizeof(ClassList) + sizeof(Class *) * newCount); + newList->count = newCount; + for (int32_t i = 0; i < oldCount; i++) { + newList->classes[i] = oldList->classes[i]; + Ptr_retain(newList->classes[i]); + } + newList->classes[oldCount] = proto; + // proto was retained at the start of function. + + if (atomic_compare_exchange_strong_explicit( + &self->implementedProtocols, &oldList, newList, + memory_order_release, memory_order_relaxed)) { + if (oldList) { + BridgedObject *bridge = + BridgedObject_create(oldList, reclaim_class_list_destructor, NULL); + autorelease(RT_boxPtr(bridge)); + } + break; + } else { + // Failed CAS, cleanup newList and retry + for (int32_t i = 0; i < newCount - 1; i++) { + Ptr_release(newList->classes[i]); + } + // We keep proto retained for the next attempt + deallocate(newList); + } + } +} diff --git a/backend-v2/runtime/Class.h b/backend-v2/runtime/Class.h index 99690754..8a05b30a 100644 --- a/backend-v2/runtime/Class.h +++ b/backend-v2/runtime/Class.h @@ -15,23 +15,23 @@ extern "C" { #include typedef struct Object Object; -typedef struct Class Interface; +typedef struct Class Protocol; -typedef struct ImplementedInterface { - Interface *interface; - ClojureFunction **functions; -} ImplementedInterface; +typedef struct ClassList { + int32_t count; + struct Class *classes[]; +} ClassList; typedef struct Class { Object super; int32_t registerId; - bool isInterface; + bool isProtocol; String *name; String *className; - int32_t superclassCount; - struct Class **superclasses; + _Atomic(struct ClassList *) superclasses; + _Atomic(struct ClassList *) implementedProtocols; void *compilerExtension; void (*compilerExtensionDestructor)(void *); @@ -42,9 +42,11 @@ typedef struct Class { Class *Class_create(String *name, String *className, int32_t superclassCount, Class **superclasses); -Class *Class_createInterface(String *name, String *className, - int32_t extendsInterfaceCount, - Class **extendsInterfaces); +Class *Class_createProtocol(String *name, String *className, + int32_t extendsProtocolCount, + Class **extendsProtocols); + +void Class_addProtocol(Class *self, Protocol *proto); bool Class_equals(Class *self, Class *other); int32_t Class_hash(Class *self); diff --git a/backend-v2/runtime/Object.c b/backend-v2/runtime/Object.c index dda765ad..ae6ba48f 100644 --- a/backend-v2/runtime/Object.c +++ b/backend-v2/runtime/Object.c @@ -8,6 +8,7 @@ _Atomic(uword_t) allocationCount[256]; _Atomic(uword_t) objectCount[256]; +_Atomic(uword_t) globalMethodICEpoch = 0; _Thread_local void *memoryBank[8] = {0}; _Thread_local int memoryBankSize[8] = {0}; diff --git a/backend-v2/runtime/Object.h b/backend-v2/runtime/Object.h index b1522ee0..4914476d 100644 --- a/backend-v2/runtime/Object.h +++ b/backend-v2/runtime/Object.h @@ -54,6 +54,7 @@ void printReferenceCounts(); extern _Atomic(uword_t) allocationCount[256]; extern _Atomic(uword_t) objectCount[256]; +extern _Atomic(uword_t) globalMethodICEpoch; #define TRACING_LIMIT 256 // bank 0 - 32 bytes 2^5 diff --git a/backend-v2/runtime/PersistentList.c b/backend-v2/runtime/PersistentList.c index d5ca6341..d43a7422 100644 --- a/backend-v2/runtime/PersistentList.c +++ b/backend-v2/runtime/PersistentList.c @@ -84,6 +84,10 @@ PersistentList *PersistentList_create(RTValue first, PersistentList *rest) { return self; } +bool PersistentList_equals_managed(PersistentList *self, RTValue other) { + return equals_managed(RT_boxPtr(self), other); +} + /* outside refcount system */ bool PersistentList_equals(PersistentList *self, PersistentList *other) { if (self->count != other->count) @@ -142,6 +146,38 @@ String *PersistentList_toString(PersistentList *self) { return retVal; } +int32_t PersistentList_count(PersistentList *self) { + int32_t retVal = self->count; + Ptr_release(self); + return retVal; +} + +PersistentList *PersistentList_identity(PersistentList *self) { return self; } + +RTValue PersistentList_next(PersistentList *self) { + if (self->rest == NULL) { + Ptr_release(self); + return RT_boxNil(); + } + + PersistentList *next = self->rest; + Ptr_retain(next); + Ptr_release(self); + return RT_boxPtr(next); +} + +PersistentList *PersistentList_pop(PersistentList *self) { + if (self->rest == NULL) { + Ptr_release(self); + return PersistentList_empty(); + } + + PersistentList *next = self->rest; + Ptr_retain(next); + Ptr_release(self); + return next; +} + /* outside refcount system */ void PersistentList_destroy(PersistentList *self, bool deallocateChildren) { if (deallocateChildren) { @@ -177,6 +213,17 @@ PersistentList *PersistentList_conj(PersistentList *self, RTValue other) { return PersistentList_create(other, self); } +RTValue PersistentList_first(PersistentList *self) { + RTValue first = self->first; + if (!RT_isNull(first)) { + retain(first); + Ptr_release(self); + return first; + } + Ptr_release(self); + return RT_boxNull(); +} + PersistentList *PersistentList_fromArray(int32_t argCount, RTValue *args) { PersistentList *retVal = PersistentList_empty(); for (int32_t i = argCount - 1; i >= 0; i--) { diff --git a/backend-v2/runtime/PersistentList.h b/backend-v2/runtime/PersistentList.h index 25a78195..8d594d98 100644 --- a/backend-v2/runtime/PersistentList.h +++ b/backend-v2/runtime/PersistentList.h @@ -15,7 +15,7 @@ struct PersistentList { Object super; RTValue first; PersistentList *rest; - uint64_t count; + int32_t count; }; void PersistentList_initialise(); @@ -23,6 +23,7 @@ PersistentList *PersistentList_empty(); void PersistentList_cleanup(); bool PersistentList_equals(PersistentList *self, PersistentList *other); +bool PersistentList_equals_managed(PersistentList *self, RTValue other); uint64_t PersistentList_hash(PersistentList *self); String *PersistentList_toString(PersistentList *self); void PersistentList_destroy(PersistentList *self, bool deallocateChildren); @@ -34,6 +35,11 @@ PersistentList *PersistentList_createMany(int32_t argCount, ...); PersistentList *PersistentList_fromArray(int32_t argCount, RTValue *args); RTValue RT_createListFromArray(int32_t argCount, RTValue *args); +int32_t PersistentList_count(PersistentList *self); +PersistentList *PersistentList_identity(PersistentList *self); +RTValue PersistentList_next(PersistentList *self); +RTValue PersistentList_first(PersistentList *self); + #ifdef __cplusplus } #endif diff --git a/backend-v2/state/ThreadsafeCompilerState.cpp b/backend-v2/state/ThreadsafeCompilerState.cpp index db68072b..18b0cab1 100644 --- a/backend-v2/state/ThreadsafeCompilerState.cpp +++ b/backend-v2/state/ThreadsafeCompilerState.cpp @@ -6,6 +6,9 @@ namespace rt { extern "C" void delete_class_description(void *ptr); +extern "C" _Atomic(uword_t) globalMethodICEpoch; + +ThreadsafeCompilerState::~ThreadsafeCompilerState() {} void ThreadsafeCompilerState::storeInternalClasses(RTValue from) { auto descriptions = buildClasses(from); @@ -26,80 +29,373 @@ void ThreadsafeCompilerState::storeInternalClasses(RTValue from) { c->compilerExtensionDestructor = delete_class_description; } - // Phase 2: Link inheritance - for (auto const &pair : localMap) { - ::Class *c = pair.second; - ClassDescription *desc = - static_cast(c->compilerExtension); + try { + // 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; - } else { - // Look in global registry - parentClass = classRegistry.getCurrent(desc->parentName.c_str()); - // getCurrent already retains for us + if (!desc->parentName.empty() || !desc->parentNames.empty()) { + // Populate runtime Class superclasses + int32_t count = (int32_t)desc->parentNames.size(); + ClassList *list = + (ClassList *)allocate(sizeof(ClassList) + sizeof(::Class *) * count); + list->count = count; + for (int32_t i = 0; i < count; i++) { + ::Class *pc = nullptr; + auto it2 = localMap.find(desc->parentNames[i]); + if (it2 != localMap.end()) { + pc = it2->second; + Ptr_retain(pc); + } else { + pc = classRegistry.getCurrent(desc->parentNames[i].c_str()); + if (!pc) { + pc = protocolRegistry.getCurrent(desc->parentNames[i].c_str()); + } + } + if (pc) { + list->classes[i] = pc; + if (i == 0) { + desc->extends = pc; + Ptr_retain(pc); // Extra retain for desc->extends + } + } else { + throwInternalInconsistencyException( + "Parent class/protocol not found: " + desc->parentNames[i]); + } + } + // Atomic store to publish superclasses. + // Note: The initial empty list from Class_create is leaked here unless we destroy it. + // But Class_create was called with 0 superclasses, so it allocated an empty ClassList. + ClassList *oldList = atomic_load_explicit(&c->superclasses, memory_order_relaxed); + atomic_store_explicit(&c->superclasses, list, memory_order_relaxed); + if (oldList) deallocate(oldList); + } } - if (parentClass) { - // desc release parentClass in its destructor. - Ptr_retain(parentClass); - desc->extends = parentClass; - - // Populate runtime Class superclasses (currently support single - // inheritance) - // Class_destroy also releases superclasses. - Ptr_retain(parentClass); - c->superclassCount = 1; - c->superclasses = (::Class **)allocate(sizeof(::Class *)); - c->superclasses[0] = parentClass; - - // If it came from registry, release the reference from getCurrent - if (it == localMap.end()) { - Ptr_release(parentClass); + // Phase 3: Register and cleanup local references + for (auto const &pair : localMap) { + ::Class *c = pair.second; + ClassDescription *desc = + static_cast(c->compilerExtension); + + Ptr_retain(c); // For name-based registry + classRegistry.registerObject(desc->name.c_str(), c); + + if (desc->type.isDetermined()) { + c->registerId = (int32_t)desc->type.determinedType(); } - } else { - for (auto const &p2 : localMap) - Ptr_release(p2.second); - throwInternalInconsistencyException("Parent class not found: " + - desc->parentName); + + Ptr_retain(c); // For ID-based registry + if (desc->type.isDetermined()) { + classRegistry.registerObject(c, c->registerId); + } else { + c->registerId = (int32_t)classRegistry.registerObject(c, -1); + } + + // Release the initial reference from Phase 1 + Ptr_release(c); } + } catch (...) { + for (auto const &pair : localMap) { + Ptr_release(pair.second); + } + throw; } - } - // Phase 3: Register and cleanup local references + // Phase 4: Link implemented protocols for (auto const &pair : localMap) { ::Class *c = pair.second; ClassDescription *desc = static_cast(c->compilerExtension); - // Give our local reference to the registry (registerObject name-based) - Ptr_retain(c); - classRegistry.registerObject(desc->name.c_str(), c); + if (!desc->implements.empty()) { + std::vector<::Class *> classImpls; + for (auto const &[protoName, protoMethods] : desc->implements) { + ::Class *proto = protocolRegistry.getCurrent(protoName.c_str()); + if (!proto) { + continue; + } + // proto was already retained by getCurrent. Ownership transferred to c->implementedProtocols. + classImpls.push_back(proto); + } - if (desc->type.isDetermined()) { - c->registerId = (int32_t)desc->type.determinedType(); + if (!classImpls.empty()) { + int32_t count = (int32_t)classImpls.size(); + ClassList *list = + (ClassList *)allocate(sizeof(ClassList) + sizeof(::Class *) * count); + list->count = count; + for (int i = 0; i < count; i++) { + list->classes[i] = classImpls[i]; + } + atomic_store_explicit(&c->implementedProtocols, list, memory_order_relaxed); + } + } + } + + try { + std::vector<::Class *> classes; + for (auto const &pair : localMap) { + classes.push_back(pair.second); } + validateProtocolImplementations(classes); + } catch (...) { + // Note: localMap objects are already registered in classRegistry, + // so they will be cleaned up by classRegistry's destructor if we + // let the exception propagate and the caller destroys the state. + throw; + } + atomic_fetch_add_explicit(&globalMethodICEpoch, 1, memory_order_release); +} - Ptr_retain(c); +void ThreadsafeCompilerState::storeInternalProtocols(RTValue from) { + auto descriptions = buildClasses(from); + unordered_map localMap; + + // Phase 1: Create all hulls + for (auto &desc : descriptions) { + String *nameStr = String_createDynamicStr(desc->name.c_str()); + String *classNameStr = String_createDynamicStr(desc->name.c_str()); + ::Class *c = Class_createProtocol(nameStr, classNameStr, 0, NULL); + localMap[desc->name] = c; if (desc->type.isDetermined()) { - // If we register by ID too, we need another reference because the ID - // registry also releases on its own. - classRegistry.registerObject(c, c->registerId); - } else { - c->registerId = (int32_t)classRegistry.registerObject(c, -1); + c->registerId = desc->type.determinedType(); } + c->compilerExtension = desc.release(); + c->compilerExtensionDestructor = delete_class_description; + } + + try { + // Phase 2: Link inheritance (protocols can extend other protocols) + for (auto const &pair : localMap) { + ::Class *c = pair.second; + ClassDescription *desc = + static_cast(c->compilerExtension); - // Release the initial reference from Phase 1 - Ptr_release(c); + if (!desc->parentNames.empty()) { + int32_t count = (int32_t)desc->parentNames.size(); + ClassList *list = + (ClassList *)allocate(sizeof(ClassList) + sizeof(::Class *) * count); + list->count = count; + for (int32_t i = 0; i < count; i++) { + ::Class *pc = nullptr; + auto it = localMap.find(desc->parentNames[i]); + if (it != localMap.end()) { + pc = it->second; + Ptr_retain(pc); + } else { + pc = protocolRegistry.getCurrent(desc->parentNames[i].c_str()); + if (!pc) { + pc = classRegistry.getCurrent(desc->parentNames[i].c_str()); + } + } + if (pc) { + list->classes[i] = pc; + // Class_destroy will release each superclass, so we need a retain + // here (the one from getCurrent/localMap is consumed here) + if (i == 0) { + desc->extends = pc; + Ptr_retain(pc); // Extra retain for desc->extends which is released in ~ClassDescription + } + } else { + throwInternalInconsistencyException( + "Extended protocol not found: " + desc->parentNames[i]); + } + } + ClassList *oldList = atomic_load_explicit(&c->superclasses, memory_order_relaxed); + atomic_store_explicit(&c->superclasses, list, memory_order_relaxed); + if (oldList) deallocate(oldList); + } + } + + // Phase 3: Register + for (auto const &pair : localMap) { + ::Class *c = pair.second; + ClassDescription *desc = + static_cast(c->compilerExtension); + + Ptr_retain(c); // For name-based registry + protocolRegistry.registerObject(desc->name.c_str(), c); + + if (desc->type.isDetermined()) { + c->registerId = (int32_t)desc->type.determinedType(); + } + Ptr_retain(c); // For ID-based registry + if (desc->type.isDetermined()) { + protocolRegistry.registerObject(c, c->registerId); + } else { + c->registerId = (int32_t)protocolRegistry.registerObject(c, -1); + } + + // Release the initial reference from Phase 1 + Ptr_release(c); + } + } catch (...) { + for (auto const &pair : localMap) { + Ptr_release(pair.second); + } + throw; } + atomic_fetch_add_explicit(&globalMethodICEpoch, 1, memory_order_release); } -void ThreadsafeCompilerState::storeInternalProtocols(RTValue from) { - // Protocols can be handled similarly if needed - release(from); +void ThreadsafeCompilerState::validateProtocolImplementations( + const std::vector<::Class *> &classes) { + // 1. Validate classes + size_t i = 0; + try { + for (; i < classes.size(); i++) { + ::Class *c = classes[i]; + ClassDescription *desc = + static_cast(c->compilerExtension); + if (!desc) { + continue; + } + + for (auto const &[protoName, implMethods] : desc->implements) { + ::Class *proto = protocolRegistry.getCurrent(protoName.c_str()); + if (!proto) + continue; // Should have been caught earlier + + // Helper to collect all required methods from protocol hierarchy + unordered_map> requiredMethods; + vector<::Class *> queue; + queue.push_back(proto); + unordered_set<::Class *> visited; + + try { + while (!queue.empty()) { + ::Class *curr = queue.back(); + queue.pop_back(); + if (visited.count(curr)) { + Ptr_release(curr); + continue; + } + visited.insert(curr); + + ClassDescription *currDesc = + static_cast(curr->compilerExtension); + if (currDesc) { + for (auto const &[name, fns] : currDesc->instanceFns) { + for (auto const &fn : fns) { + requiredMethods[name].push_back(fn); + } + } + // Also add parents of this protocol + ClassList *supers = atomic_load_explicit(&curr->superclasses, memory_order_acquire); + if (supers) { + for (int j = 0; j < supers->count; j++) { + ::Class *parent = supers->classes[j]; + Ptr_retain(parent); + queue.push_back(parent); + } + } + } + } + + // Validate each required method + for (auto const &[methodName, protoFns] : requiredMethods) { + // Find all implementations of this method across all protocol blocks of the class + std::vector allImpls; + for (auto const &[anyProtoName, anyImplMethods] : desc->implements) { + auto it = anyImplMethods.find(methodName); + if (it != anyImplMethods.end()) { + for (auto const &implFn : it->second) { + allImpls.push_back(&implFn); + } + } + } + + if (allImpls.empty()) { + throwInternalInconsistencyException( + "Class " + desc->name + + " is missing implementation of method " + methodName + + " from protocol " + protoName); + } + + for (auto const &protoFn : protoFns) { + // Check if there's an implementation for this arity + bool found = false; + for (auto const &implFnPtr : allImpls) { + const auto &implFn = *implFnPtr; + if (implFn.argTypes.size() == protoFn.argTypes.size()) { + // Check signature matches + bool signatureMatches = true; + for (size_t k = 0; k < protoFn.argTypes.size(); k++) { + // The first argument is :this, which will differ between + // protocol and implementing class. + if (k == 0 && protoFn.isInstance && implFn.isInstance) + continue; + + if (protoFn.argTypes[k] != ObjectTypeSet::dynamicType() && + protoFn.argTypes[k] != implFn.argTypes[k]) { + signatureMatches = false; + break; + } + } + + if (signatureMatches) { + found = true; + break; + } + } + } + if (!found) { + throwInternalInconsistencyException( + "Class " + desc->name + " missing arity " + + to_string(protoFn.argTypes.size()) + " for method " + + methodName + " of protocol " + protoName); + } + } + } + + // Check for extra methods in :implements + for (auto const &[methodName, implFns] : implMethods) { + if (requiredMethods.find(methodName) == requiredMethods.end()) { + throwInternalInconsistencyException( + "Class " + desc->name + " implements extra method " + + methodName + " not found in protocol " + protoName + + " or its parents"); + } + } + + // Check for conflicts with regular methods + for (auto const &[methodName, implFns] : implMethods) { + auto it = desc->instanceFns.find(methodName); + if (it != desc->instanceFns.end()) { + for (auto const &protoFn : implFns) { + for (auto const ®Fn : it->second) { + if (regFn.argTypes.size() == protoFn.argTypes.size()) { + throwInternalInconsistencyException( + "Class " + desc->name + " has regular method " + + methodName + " that conflicts with protocol " + + protoName + " implementation of the same arity (" + + to_string(protoFn.argTypes.size()) + ")"); + } + } + } + } + } + } catch (...) { + for (auto vproto : visited) { + Ptr_release(vproto); + } + for (auto qproto : queue) { + Ptr_release(qproto); + } + throw; + } + + // Release all visited protocols (collected in the queue process) + for (auto vproto : visited) { + Ptr_release(vproto); + } + } + } + } catch (...) { + throw; + } } } // namespace rt diff --git a/backend-v2/state/ThreadsafeCompilerState.h b/backend-v2/state/ThreadsafeCompilerState.h index 75446155..17f70946 100644 --- a/backend-v2/state/ThreadsafeCompilerState.h +++ b/backend-v2/state/ThreadsafeCompilerState.h @@ -20,15 +20,22 @@ namespace rt { class ThreadsafeCompilerState { public: ThreadsafeRegistry<::Class> classRegistry; + ThreadsafeRegistry<::Class> protocolRegistry; ThreadsafeRegistry functionAstRegistry; ThreadsafeRegistry<::Var> varRegistry; ThreadsafeCompilerState() - : classRegistry(true, 1000), functionAstRegistry(false), - varRegistry(true) {} + : classRegistry(true, 1000), protocolRegistry(true, 2000), + functionAstRegistry(false), varRegistry(true) {} + + ~ThreadsafeCompilerState(); void storeInternalClasses(RTValue from); void storeInternalProtocols(RTValue from); + + void validateProtocolImplementations(const std::vector<::Class *> &classes); + +private: }; } // namespace rt diff --git a/backend-v2/tests/CMakeLists.txt b/backend-v2/tests/CMakeLists.txt index b7b066d3..3cc117cc 100644 --- a/backend-v2/tests/CMakeLists.txt +++ b/backend-v2/tests/CMakeLists.txt @@ -19,6 +19,7 @@ set(TEST_MODULES codegen/MathIntrinsicsRepro_test codegen/InstanceCallNode_test codegen/InstanceCallIC_test + codegen/ProtocolInstanceCall_test codegen/VoidInvokeRepro_test codegen/DCE_test codegen/O3_DebugInfo_test @@ -43,6 +44,7 @@ set(TEST_MODULES codegen/RecurNode_test codegen/EBRFlush_test state/ClassRegistration_test + state/ProtocolValidation_test VarUAF_repro_test JITSafety_test ) diff --git a/backend-v2/tests/JITSafety_test.cpp b/backend-v2/tests/JITSafety_test.cpp index 5ddddfa9..d44b83d4 100644 --- a/backend-v2/tests/JITSafety_test.cpp +++ b/backend-v2/tests/JITSafety_test.cpp @@ -142,7 +142,6 @@ static void test_compilation_error_concurrency(void **state_arg) { // We need a real String object for the receiver RTValue strObj = RT_boxPtr(String_create("test")); - retain(strObj); auto t1 = std::thread([&]() { while (!start) @@ -218,7 +217,7 @@ void test_inheritance_bigint_tostring(void **state) { InstanceCallSlowPath(&icSlot, "toString", 0, args, 0, &engine); assert_non_null(bridgePtr); - assert_int_equal(icSlot.key, bigIntegerType); + assert_int_equal((icSlot.key & 0xFFFFFFFFULL), (uint64_t)bigIntegerType); // Call the bridge. Specialized bridge for 0-arg method takes 1 arg // (instance). diff --git a/backend-v2/tests/codegen/AsyncStackTrace_test.cpp b/backend-v2/tests/codegen/AsyncStackTrace_test.cpp index 46958502..4e46f2a6 100644 --- a/backend-v2/tests/codegen/AsyncStackTrace_test.cpp +++ b/backend-v2/tests/codegen/AsyncStackTrace_test.cpp @@ -60,8 +60,7 @@ static void test_async_stack_trace_friendly(void **state) { try { RTValue args[1] = {strObj}; - // Slow path has a peculiar semantics. It does not consume when successful - // and it does when throws. + // Slow path NEVER consumes arguments. InstanceCallSlowPath(&icSlot, "contas", 0, args, 0, &engine); fail_msg("Expected an exception from .contas"); } catch (const LanguageException &e) { @@ -82,6 +81,7 @@ static void test_async_stack_trace_friendly(void **state) { // 3. But it should show the main test thread (the end of the chain) assert_non_null(strstr(trace.c_str(), "test_async_stack_trace_friendly")); } + release(strObj); }); } @@ -96,8 +96,7 @@ static void test_async_stack_trace_debug(void **state) { try { RTValue args[1] = {strObj}; - // Slow path has a peculiar semantics. It does not consume when successful - // and it does when throws. + // Slow path NEVER consumes arguments. InstanceCallSlowPath(&icSlot, "contas", 0, args, 0, &engine); fail_msg("Expected an exception from .contas"); } catch (const LanguageException &e) { @@ -109,6 +108,7 @@ static void test_async_stack_trace_debug(void **state) { assert_non_null(strstr(trace.c_str(), "InstanceCallSlowPath")); assert_non_null(strstr(trace.c_str(), "test_async_stack_trace_debug")); } + release(strObj); }); } diff --git a/backend-v2/tests/codegen/IsInstanceNode_test.cpp b/backend-v2/tests/codegen/IsInstanceNode_test.cpp index 00a2b86f..68f73f6c 100644 --- a/backend-v2/tests/codegen/IsInstanceNode_test.cpp +++ b/backend-v2/tests/codegen/IsInstanceNode_test.cpp @@ -4,10 +4,12 @@ #include "../../tools/EdnParser.h" #include "bytecode.pb.h" #include "runtime/ObjectProto.h" -#include -#include - extern "C" { +#include "../../runtime/PersistentArrayMap.h" +#include "../../runtime/PersistentVector.h" +#include "../../runtime/Keyword.h" +#include "../../runtime/Symbol.h" +#include "../../runtime/String.h" #include "../../runtime/tests/TestTools.h" #include } @@ -27,6 +29,12 @@ RTValue MyClass_create() { return (RTValue)(uintptr_t)obj; } +RTValue C1_create() { + Object *obj = (Object *)malloc(sizeof(Object)); + Object_create(obj, (objectType)1001); + return (RTValue)(uintptr_t)obj; +} + RTValue someMethod() { return RT_boxNil(); } } @@ -222,6 +230,39 @@ static void test_is_instance_class_prefix(void **state) { }); } +static void test_is_instance_interface_prefix(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + JITEngine engine; + ThreadsafeCompilerState &compilerState = engine.threadsafeState; + setup_test_metadata(compilerState); + + Node node; + node.set_op(opIsInstance); + auto *is = node.mutable_subnode()->mutable_isinstance(); + is->set_class_("interface MyClass"); + + auto *target = is->mutable_target(); + target->set_op(opNew); + auto *nn = target->mutable_subnode()->mutable_new_(); + auto *clsNode = nn->mutable_class_(); + clsNode->set_op(opConst); + clsNode->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeClass); + clsNode->mutable_subnode()->mutable_const_()->set_val("MyClass"); + + auto res = engine + .compileAST(node, "test_is_instance_interface_prefix") + .get() + .address; + + RTValue result = resPtrToValue(res); + assert_true(RT_isBool(result)); + assert_true(RT_unboxBool(result)); + release(result); + }); +} + static void test_is_instance_any_type_regression(void **state) { (void)state; ASSERT_MEMORY_ALL_BALANCED({ @@ -314,6 +355,80 @@ static void test_is_instance_refcounted_local(void **state) { }); } +static void test_is_instance_protocol(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::JITEngine engine; + rt::ThreadsafeCompilerState &compState = engine.threadsafeState; + + // 1. Define Protocol P1 + PersistentArrayMap *p1Map = PersistentArrayMap_empty(); + p1Map = PersistentArrayMap_assoc( + p1Map, Keyword_create(String_create("is-interface")), RT_boxBool(true)); + p1Map = PersistentArrayMap_assoc( + p1Map, Keyword_create(String_create("name")), + RT_boxPtr(String_create("P1"))); + p1Map = PersistentArrayMap_assoc( + p1Map, Keyword_create(String_create("object-type")), RT_boxInt32(2001)); + + PersistentArrayMap *protoRoot = PersistentArrayMap_empty(); + protoRoot = PersistentArrayMap_assoc( + protoRoot, Symbol_create(String_create("P1")), RT_boxPtr(p1Map)); + compState.storeInternalProtocols(RT_boxPtr(protoRoot)); + + // 2. Define Class C1 implementing P1 + PersistentArrayMap *c1Map = PersistentArrayMap_empty(); + c1Map = PersistentArrayMap_assoc( + c1Map, Keyword_create(String_create("object-type")), RT_boxInt32(1001)); + c1Map = PersistentArrayMap_assoc(c1Map, Keyword_create(String_create("name")), + RT_boxPtr(String_create("C1"))); + + PersistentArrayMap *p1Impl = PersistentArrayMap_empty(); + PersistentArrayMap *impls = PersistentArrayMap_empty(); + impls = PersistentArrayMap_assoc( + impls, Symbol_create(String_create("P1")), RT_boxPtr(p1Impl)); + c1Map = PersistentArrayMap_assoc( + c1Map, Keyword_create(String_create("implements")), RT_boxPtr(impls)); + + PersistentVector *ctors = PersistentVector_create(); + PersistentArrayMap *ctorDesc = PersistentArrayMap_empty(); + ctorDesc = PersistentArrayMap_assoc(ctorDesc, Keyword_create(String_create("type")), Keyword_create(String_create("call"))); + ctorDesc = PersistentArrayMap_assoc(ctorDesc, Keyword_create(String_create("symbol")), RT_boxPtr(String_create("C1_create"))); + ctorDesc = PersistentArrayMap_assoc(ctorDesc, Keyword_create(String_create("args")), RT_boxPtr(PersistentVector_create())); + ctors = PersistentVector_conj(ctors, RT_boxPtr(ctorDesc)); + c1Map = PersistentArrayMap_assoc(c1Map, Keyword_create(String_create("constructor")), RT_boxPtr(ctors)); + + PersistentArrayMap *classRoot = PersistentArrayMap_empty(); + classRoot = PersistentArrayMap_assoc( + classRoot, Symbol_create(String_create("C1")), RT_boxPtr(c1Map)); + compState.storeInternalClasses(RT_boxPtr(classRoot)); + + // 3. Test (instance? P1 (C1.)) + Node node; + node.set_op(opIsInstance); + auto *is = node.mutable_subnode()->mutable_isinstance(); + is->set_class_("P1"); + + auto *target = is->mutable_target(); + target->set_op(opNew); + auto *nn = target->mutable_subnode()->mutable_new_(); + nn->mutable_class_()->set_op(opConst); + nn->mutable_class_()->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeClass); + nn->mutable_class_()->mutable_subnode()->mutable_const_()->set_val("C1"); + + auto res = engine + .compileAST(node, "test_is_instance_protocol") + .get() + .address; + + RTValue result = resPtrToValue(res); + assert_true(RT_isBool(result)); + assert_true(RT_unboxBool(result)); + release(result); + }); +} + int main(void) { initialise_memory(); const struct CMUnitTest tests[] = { @@ -321,8 +436,10 @@ int main(void) { cmocka_unit_test(test_is_instance_compile_time_false), cmocka_unit_test(test_is_instance_runtime), cmocka_unit_test(test_is_instance_class_prefix), + cmocka_unit_test(test_is_instance_interface_prefix), cmocka_unit_test(test_is_instance_any_type_regression), cmocka_unit_test(test_is_instance_refcounted_local), + cmocka_unit_test(test_is_instance_protocol), }; int result = cmocka_run_group_tests(tests, NULL, NULL); diff --git a/backend-v2/tests/codegen/ProtocolInstanceCall_test.cpp b/backend-v2/tests/codegen/ProtocolInstanceCall_test.cpp new file mode 100644 index 00000000..333d4df8 --- /dev/null +++ b/backend-v2/tests/codegen/ProtocolInstanceCall_test.cpp @@ -0,0 +1,154 @@ +#include "../../codegen/CodeGen.h" +#include "../../jit/JITEngine.h" +#include "../../runtime/RuntimeInterface.h" +#include "../../state/ThreadsafeCompilerState.h" +#include "../../tools/EdnParser.h" +#include "bytecode.pb.h" +#include "runtime/Ebr.h" +#include +#include + +extern "C" { +#include "../../runtime/Keyword.h" +#include "../../runtime/PersistentList.h" +#include "../../runtime/PersistentVector.h" +#include "../../runtime/String.h" +#include "../../runtime/Var.h" +#include "../../runtime/tests/TestTools.h" +#include +} + +#include +#include + +using namespace std; +using namespace rt; +using namespace clojure::rt::protobuf::bytecode; + +extern "C" _Atomic(uword_t) globalMethodICEpoch; + +// Mock implementations +extern "C" { +int32_t mock_P1_m1_v1(PersistentVector *instance, int32_t x) { + Ptr_release(instance); + return 100 + x; +} +int32_t mock_P1_m1_v2(PersistentVector *instance, int32_t x) { + Ptr_release(instance); + return 200 + x; +} +} + +static void test_protocol_instance_call_lookup_and_invalidation(void **state) { + (void)state; + + ASSERT_MEMORY_ALL_BALANCED({ + JITEngine engine; + rt::ThreadsafeCompilerState &compState = engine.threadsafeState; + + // 1. Define Protocol P1 + { + String *nameStr = String_create("P1"); + String *classNameStr = String_create("P1"); + Class *cls = Class_createProtocol(nameStr, classNameStr, 0, nullptr); + ClassDescription *ext = new ClassDescription(); + ext->name = "P1"; + ext->isProtocol = true; + ext->type = ObjectTypeSet(classType); // Protocols use classType for now + cls->compilerExtension = ext; + cls->compilerExtensionDestructor = delete_class_description; + compState.protocolRegistry.registerObject("P1", cls); + } + + // 2. Define Class C1 implementing P1 + auto setupC1 = [&](const string& symbol) { + String *nameStr = String_create("C1"); + String *classNameStr = String_create("C1"); + Class *cls = Class_create(nameStr, classNameStr, 0, nullptr); + ClassDescription *ext = new ClassDescription(); + ext->name = "C1"; + ext->type = ObjectTypeSet((objectType)persistentVectorType); + + IntrinsicDescription m1; + m1.symbol = symbol; + m1.type = CallType::Call; + m1.argTypes.push_back(ObjectTypeSet((objectType)persistentVectorType)); + m1.argTypes.push_back(ObjectTypeSet((objectType)integerType, false)); + m1.returnType = ObjectTypeSet((objectType)integerType, false); + + // Add implementation under protocol P1 + ext->implements["P1"]["m1"].push_back(m1); + + cls->compilerExtension = ext; + cls->compilerExtensionDestructor = delete_class_description; + compState.classRegistry.registerObject("C1", cls); + Ptr_retain(cls); // Registry doesn't retain on register, and we have two registries + compState.classRegistry.registerObject(cls, (int32_t)persistentVectorType); + atomic_fetch_add_explicit(&globalMethodICEpoch, 1, memory_order_release); + }; + + setupC1("mock_P1_m1_v1"); + + // Register Var user/x + RTValue varKeyword = Keyword_create(String_create("user/x")); + Var *myVar = Var_create(varKeyword); + compState.varRegistry.registerObject("user/x", myVar); + + // AST: (.m1 @user/x 10) + Node callNode; + callNode.set_op(opInstanceCall); + auto *ic = callNode.mutable_subnode()->mutable_instancecall(); + ic->set_method("m1"); + auto *inst = ic->mutable_instance(); + inst->set_op(opVar); + inst->mutable_subnode()->mutable_var()->set_var("#'user/x"); + auto *arg = ic->add_args(); + arg->set_op(opConst); + arg->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg->mutable_subnode()->mutable_const_()->set_val("10"); + arg->set_tag("long"); + + try { + auto resF = engine.compileAST(callNode, "test_proto_call").get().address; + auto fn = resF.toPtr(); + + // --- Phase 1: Call v1 --- + RTValue vec = RT_boxPtr(PersistentVector_create()); + Ptr_retain(myVar); + Var_bindRoot(myVar, vec); + + RTValue r1 = fn(); + assert_int_equal(110, RT_unboxInt32(r1)); + release(r1); + + // --- Phase 2: Warm IC --- + RTValue r2 = fn(); + assert_int_equal(110, RT_unboxInt32(r2)); + release(r2); + + // --- Phase 3: Redefine C1 (v2) --- + // This should increment globalMethodICEpoch + setupC1("mock_P1_m1_v2"); + + // --- Phase 4: Call again (should pick up v2 because IC was invalidated) --- + RTValue r3 = fn(); + assert_int_equal(210, RT_unboxInt32(r3)); + release(r3); + + // Cleanup + Ptr_retain(myVar); + Var_bindRoot(myVar, RT_boxNil()); + } catch (const std::exception &e) { + fprintf(stderr, "Exception: %s\n", e.what()); + assert_true(false); + } + }); +} + +int main(void) { + initialise_memory(); + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_protocol_instance_call_lookup_and_invalidation), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/backend-v2/tests/state/ProtocolValidation_test.cpp b/backend-v2/tests/state/ProtocolValidation_test.cpp new file mode 100644 index 00000000..41f1ff53 --- /dev/null +++ b/backend-v2/tests/state/ProtocolValidation_test.cpp @@ -0,0 +1,242 @@ +#include "../../RuntimeHeaders.h" +#include "../../jit/JITEngine.h" +#include "../../state/ThreadsafeCompilerState.h" +#include "../../tools/EdnParser.h" +#include "bytecode.pb.h" +#include +#include +#include +#include + +extern "C" { +#include "../../runtime/Keyword.h" +#include "../../runtime/PersistentArrayMap.h" +#include "../../runtime/PersistentVector.h" +#include "../../runtime/RuntimeInterface.h" +#include "../../runtime/String.h" +#include "../../runtime/Symbol.h" +#include "../../runtime/tests/TestTools.h" +#include +} + +#include "../../bridge/Exceptions.h" + +using namespace std; +using namespace rt; + +static void test_protocol_inheritance_and_isinstance(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::JITEngine engine; + rt::ThreadsafeCompilerState &compState = engine.threadsafeState; + + // 1. Define Protocols + PersistentArrayMap *p1Map = PersistentArrayMap_empty(); + p1Map = PersistentArrayMap_assoc( + p1Map, Keyword_create(String_create("is-interface")), RT_boxBool(true)); + p1Map = PersistentArrayMap_assoc( + p1Map, Keyword_create(String_create("name")), + RT_boxPtr(String_create("P1"))); + p1Map = PersistentArrayMap_assoc( + p1Map, Keyword_create(String_create("object-type")), RT_boxInt32(2001)); + + PersistentArrayMap *p1Fns = PersistentArrayMap_empty(); + PersistentVector *p1Sig = PersistentVector_create(); + p1Sig = PersistentVector_conj(p1Sig, Keyword_create(String_create("this"))); + + PersistentArrayMap *f1Desc = PersistentArrayMap_empty(); + f1Desc = PersistentArrayMap_assoc(f1Desc, Keyword_create(String_create("args")), + RT_boxPtr(p1Sig)); + f1Desc = PersistentArrayMap_assoc( + f1Desc, Keyword_create(String_create("type")), + Keyword_create(String_create("call"))); + f1Desc = PersistentArrayMap_assoc( + f1Desc, Keyword_create(String_create("symbol")), + RT_boxPtr(String_create("f1_sym"))); + PersistentVector *f1Overloads = PersistentVector_create(); + f1Overloads = PersistentVector_conj(f1Overloads, RT_boxPtr(f1Desc)); + p1Fns = PersistentArrayMap_assoc( + p1Fns, Symbol_create(String_create("f1")), RT_boxPtr(f1Overloads)); + Ptr_retain(f1Overloads); // For p2Impl later + + p1Map = PersistentArrayMap_assoc( + p1Map, Keyword_create(String_create("instance-fns")), RT_boxPtr(p1Fns)); + + PersistentArrayMap *p2Map = PersistentArrayMap_empty(); + p2Map = PersistentArrayMap_assoc( + p2Map, Keyword_create(String_create("is-interface")), RT_boxBool(true)); + p2Map = PersistentArrayMap_assoc( + p2Map, Keyword_create(String_create("name")), + RT_boxPtr(String_create("P2"))); + p2Map = PersistentArrayMap_assoc( + p2Map, Keyword_create(String_create("object-type")), RT_boxInt32(2002)); + p2Map = PersistentArrayMap_assoc( + p2Map, Keyword_create(String_create("extends")), + Symbol_create(String_create("P1"))); + PersistentArrayMap *p2Fns = PersistentArrayMap_empty(); + PersistentVector *p2Sig = PersistentVector_create(); + p2Sig = PersistentVector_conj(p2Sig, Keyword_create(String_create("this"))); + p2Sig = PersistentVector_conj(p2Sig, Keyword_create(String_create("any"))); + + PersistentArrayMap *f2Desc = PersistentArrayMap_empty(); + f2Desc = PersistentArrayMap_assoc(f2Desc, Keyword_create(String_create("args")), + RT_boxPtr(p2Sig)); + f2Desc = PersistentArrayMap_assoc( + f2Desc, Keyword_create(String_create("type")), + Keyword_create(String_create("call"))); + f2Desc = PersistentArrayMap_assoc( + f2Desc, Keyword_create(String_create("symbol")), + RT_boxPtr(String_create("f2_sym"))); + PersistentVector *f2Overloads = PersistentVector_create(); + f2Overloads = PersistentVector_conj(f2Overloads, RT_boxPtr(f2Desc)); + p2Fns = PersistentArrayMap_assoc( + p2Fns, Symbol_create(String_create("f2")), RT_boxPtr(f2Overloads)); + Ptr_retain(f2Overloads); // For p2Impl later + + p2Map = PersistentArrayMap_assoc( + p2Map, Keyword_create(String_create("instance-fns")), RT_boxPtr(p2Fns)); + + PersistentArrayMap *protoRoot = PersistentArrayMap_empty(); + protoRoot = PersistentArrayMap_assoc( + protoRoot, Symbol_create(String_create("P1")), RT_boxPtr(p1Map)); + protoRoot = PersistentArrayMap_assoc( + protoRoot, Symbol_create(String_create("P2")), RT_boxPtr(p2Map)); + + compState.storeInternalProtocols(RT_boxPtr(protoRoot)); + + // 2. Define Class implementing P2 + PersistentArrayMap *c1Map = PersistentArrayMap_empty(); + c1Map = PersistentArrayMap_assoc( + c1Map, Keyword_create(String_create("object-type")), RT_boxInt32(1001)); + c1Map = PersistentArrayMap_assoc(c1Map, Keyword_create(String_create("name")), + RT_boxPtr(String_create("C1"))); + + PersistentArrayMap *p2Impl = PersistentArrayMap_empty(); + // P2 requires f1 (from P1) and f2 + p2Impl = PersistentArrayMap_assoc(p2Impl, Symbol_create(String_create("f1")), + RT_boxPtr(f1Overloads)); + // f1Overloads was retained above + p2Impl = PersistentArrayMap_assoc(p2Impl, Symbol_create(String_create("f2")), + RT_boxPtr(f2Overloads)); + // f2Overloads was retained above + + PersistentArrayMap *impls = PersistentArrayMap_empty(); + impls = PersistentArrayMap_assoc( + impls, Symbol_create(String_create("P2")), RT_boxPtr(p2Impl)); + c1Map = PersistentArrayMap_assoc( + c1Map, Keyword_create(String_create("implements")), RT_boxPtr(impls)); + + PersistentArrayMap *classRoot = PersistentArrayMap_empty(); + classRoot = PersistentArrayMap_assoc( + classRoot, Symbol_create(String_create("C1")), RT_boxPtr(c1Map)); + + compState.storeInternalClasses(RT_boxPtr(classRoot)); + + // 3. Verify + ::Class *clsC1 = compState.classRegistry.getCurrent("C1"); + ::Class *clsP1 = compState.protocolRegistry.getCurrent("P1"); + ::Class *clsP2 = compState.protocolRegistry.getCurrent("P2"); + + assert_non_null(clsC1); + assert_non_null(clsP1); + assert_non_null(clsP2); + + // Note: Class_isInstance won't work for protocols since they are external + // but we can check the implementedProtocols directly in the class + ClassList *list = atomic_load(&clsC1->implementedProtocols); + assert_non_null(list); + assert_int_equal(1, list->count); + assert_ptr_equal(clsP2, list->classes[0]); + + Ptr_release(clsC1); + Ptr_release(clsP1); + Ptr_release(clsP2); + }); +} + +static void test_missing_method_fails(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::JITEngine engine; + rt::ThreadsafeCompilerState &compState = engine.threadsafeState; + + // 1. Define Protocol P1 with method f1 + PersistentArrayMap *p1Map = PersistentArrayMap_empty(); + p1Map = PersistentArrayMap_assoc( + p1Map, Keyword_create(String_create("is-interface")), RT_boxBool(true)); + p1Map = PersistentArrayMap_assoc( + p1Map, Keyword_create(String_create("name")), + RT_boxPtr(String_create("P1"))); + + PersistentArrayMap *p1Fns = PersistentArrayMap_empty(); + PersistentVector *p1Sig = PersistentVector_create(); + p1Sig = PersistentVector_conj(p1Sig, Keyword_create(String_create("this"))); + + PersistentArrayMap *f1Desc = PersistentArrayMap_empty(); + f1Desc = PersistentArrayMap_assoc(f1Desc, Keyword_create(String_create("args")), + RT_boxPtr(p1Sig)); + f1Desc = PersistentArrayMap_assoc( + f1Desc, Keyword_create(String_create("type")), + Keyword_create(String_create("call"))); + f1Desc = PersistentArrayMap_assoc( + f1Desc, Keyword_create(String_create("symbol")), + RT_boxPtr(String_create("f1_sym"))); + PersistentVector *f1Overloads = PersistentVector_create(); + f1Overloads = PersistentVector_conj(f1Overloads, RT_boxPtr(f1Desc)); + p1Fns = PersistentArrayMap_assoc( + p1Fns, Symbol_create(String_create("f1")), RT_boxPtr(f1Overloads)); + p1Map = PersistentArrayMap_assoc( + p1Map, Keyword_create(String_create("instance-fns")), RT_boxPtr(p1Fns)); + + PersistentArrayMap *protoRoot = PersistentArrayMap_empty(); + protoRoot = PersistentArrayMap_assoc( + protoRoot, Symbol_create(String_create("P1")), RT_boxPtr(p1Map)); + + compState.storeInternalProtocols(RT_boxPtr(protoRoot)); + + // 2. Define Class C1 claiming to implement P1, but missing f1 + PersistentArrayMap *c1Map = PersistentArrayMap_empty(); + c1Map = PersistentArrayMap_assoc( + c1Map, Keyword_create(String_create("object-type")), RT_boxInt32(1001)); + c1Map = PersistentArrayMap_assoc(c1Map, Keyword_create(String_create("name")), + RT_boxPtr(String_create("C1"))); + + PersistentArrayMap *p1Impl = PersistentArrayMap_empty(); + // Missing f1 in the implementation map + PersistentArrayMap *impls = PersistentArrayMap_empty(); + impls = PersistentArrayMap_assoc( + impls, Symbol_create(String_create("P1")), RT_boxPtr(p1Impl)); + c1Map = PersistentArrayMap_assoc( + c1Map, Keyword_create(String_create("implements")), RT_boxPtr(impls)); + + PersistentArrayMap *classRoot = PersistentArrayMap_empty(); + classRoot = PersistentArrayMap_assoc( + classRoot, Symbol_create(String_create("C1")), RT_boxPtr(c1Map)); + + // 3. Validation should throw during storeInternalClasses because C1 is missing f1 + bool thrown = false; + try { + compState.storeInternalClasses(RT_boxPtr(classRoot)); + } catch (const rt::LanguageException &e) { + thrown = true; + } + assert_true(thrown); + + // Cleanup + ::Class *clsC1 = compState.classRegistry.getCurrent("C1"); + if (clsC1) Ptr_release(clsC1); + }); +} + +int main(void) { + initialise_memory(); + + + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_protocol_inheritance_and_isinstance), + cmocka_unit_test(test_missing_method_fails), + }; + + int result = cmocka_run_group_tests(tests, NULL, NULL); + return result; +} diff --git a/backend-v2/tests/tools/EdnParser_test.cpp b/backend-v2/tests/tools/EdnParser_test.cpp index 7a122651..6678cb54 100644 --- a/backend-v2/tests/tools/EdnParser_test.cpp +++ b/backend-v2/tests/tools/EdnParser_test.cpp @@ -825,6 +825,62 @@ static void test_intrinsic_type_invalid(void **state) { } } +static void test_protocol_signatures(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::JITEngine engine; + + // {P1 {:is-interface true, + // :instance-fns {swap [[:this :any] [:this :any :any]], + // count [:this]}}} + PersistentVector *swapSig1 = PersistentVector_create(); + swapSig1 = PersistentVector_conj(swapSig1, Keyword_create(String_create("this"))); + swapSig1 = PersistentVector_conj(swapSig1, Keyword_create(String_create("any"))); + + PersistentVector *swapSig2 = PersistentVector_create(); + swapSig2 = PersistentVector_conj(swapSig2, Keyword_create(String_create("this"))); + swapSig2 = PersistentVector_conj(swapSig2, Keyword_create(String_create("any"))); + swapSig2 = PersistentVector_conj(swapSig2, Keyword_create(String_create("any"))); + + PersistentVector *swapOverloads = PersistentVector_create(); + swapOverloads = PersistentVector_conj(swapOverloads, RT_boxPtr(swapSig1)); + swapOverloads = PersistentVector_conj(swapOverloads, RT_boxPtr(swapSig2)); + + PersistentVector *countSig = PersistentVector_create(); + countSig = PersistentVector_conj(countSig, Keyword_create(String_create("this"))); + + PersistentArrayMap *fnsMap = PersistentArrayMap_empty(); + fnsMap = PersistentArrayMap_assoc(fnsMap, Symbol_create(String_create("swap")), RT_boxPtr(swapOverloads)); + fnsMap = PersistentArrayMap_assoc(fnsMap, Symbol_create(String_create("count")), RT_boxPtr(countSig)); + + PersistentArrayMap *classMap = PersistentArrayMap_empty(); + classMap = PersistentArrayMap_assoc(classMap, Keyword_create(String_create("is-interface")), RT_boxBool(true)); + classMap = PersistentArrayMap_assoc(classMap, Keyword_create(String_create("instance-fns")), RT_boxPtr(fnsMap)); + + PersistentArrayMap *rootMap = PersistentArrayMap_empty(); + rootMap = PersistentArrayMap_assoc(rootMap, Symbol_create(String_create("P1")), RT_boxPtr(classMap)); + + auto classes = buildClasses(RT_boxPtr(rootMap)); + assert_int_equal(1, classes.size()); + + auto &c = *classes[0]; + assert_true(c.isProtocol); + + // Check swap + auto itSwap = c.instanceFns.find("swap"); + assert_true(itSwap != c.instanceFns.end()); + assert_int_equal(2, itSwap->second.size()); + assert_int_equal(2, itSwap->second[0].argTypes.size()); + assert_int_equal(3, itSwap->second[1].argTypes.size()); + + // Check count + auto itCount = c.instanceFns.find("count"); + assert_true(itCount != c.instanceFns.end()); + assert_int_equal(1, itCount->second.size()); + assert_int_equal(1, itCount->second[0].argTypes.size()); + }); +} + static void test_intrinsic_symbol_not_string(void **state) { (void)state; try { @@ -1033,9 +1089,10 @@ static void test_class_inheritance_linkage(void **state) { assert_non_null(child); // Check linkage - assert_int_equal(1, child->superclassCount); - assert_non_null(child->superclasses); - assert_ptr_equal(parent, child->superclasses[0]); + ClassList *supers = atomic_load(&child->superclasses); + assert_non_null(supers); + assert_int_equal(1, supers->count); + assert_ptr_equal(parent, supers->classes[0]); rt::ClassDescription *childDesc = static_cast(child->compilerExtension); @@ -1745,12 +1802,13 @@ static void test_class_inheritance_runtime(void **state) { assert_non_null(clsA); assert_non_null(clsB); - assert_int_equal(0, clsA->superclassCount); - assert_null(clsA->superclasses); + ClassList *supersA = atomic_load(&clsA->superclasses); + assert_true(supersA == nullptr || supersA->count == 0); - assert_int_equal(1, clsB->superclassCount); - assert_non_null(clsB->superclasses); - assert_ptr_equal(clsA, clsB->superclasses[0]); + ClassList *supersB = atomic_load(&clsB->superclasses); + assert_non_null(supersB); + assert_int_equal(1, supersB->count); + assert_ptr_equal(clsA, supersB->classes[0]); // Test Class_isInstance assert_true(Class_isInstance(clsA, clsB)); @@ -1783,6 +1841,7 @@ int main(int argc, char **argv) { cmocka_unit_test(test_intrinsic_value_not_vector), cmocka_unit_test(test_intrinsic_type_not_keyword), cmocka_unit_test(test_intrinsic_type_invalid), + cmocka_unit_test(test_protocol_signatures), cmocka_unit_test(test_intrinsic_symbol_not_string), cmocka_unit_test(test_intrinsic_args_not_vector), cmocka_unit_test(test_unknown_arg_type), diff --git a/backend-v2/tools/EdnParser.cpp b/backend-v2/tools/EdnParser.cpp index b863b8f0..1a698e0e 100644 --- a/backend-v2/tools/EdnParser.cpp +++ b/backend-v2/tools/EdnParser.cpp @@ -196,6 +196,13 @@ ClassDescription::ClassDescription(RTValue from, this->name = String_c_str(compactifiedName); Ptr_release(compactifiedName); + retain(root.get()); + ConsumedValue isInterfaceWrapper(PersistentArrayMap_get( + description, Keyword_create(String_create("is-interface")))); + if (getType(isInterfaceWrapper.get()) == booleanType) { + this->isProtocol = RT_unboxBool(isInterfaceWrapper.get()); + } + retain(root.get()); ConsumedValue extendsWrapper(PersistentArrayMap_get( description, Keyword_create(String_create("extends")))); @@ -203,7 +210,49 @@ ClassDescription::ClassDescription(RTValue from, getType(extendsWrapper.get()) == stringType) { String *sParent = String_compactify(::toString(extendsWrapper.take())); this->parentName = String_c_str(sParent); + this->parentNames.push_back(this->parentName); Ptr_release(sParent); + } else if (getType(extendsWrapper.get()) == persistentVectorType) { + PersistentVector *vec = + (PersistentVector *)RT_unboxPtr(extendsWrapper.get()); + for (uword_t i = 0; i < vec->count; i++) { + retain(RT_boxPtr(vec)); + ConsumedValue v(PersistentVector_nth(vec, i)); + String *s = String_compactify(::toString(v.take())); + this->parentNames.push_back(String_c_str(s)); + Ptr_release(s); + } + } + + retain(root.get()); + ConsumedValue implWrapper(PersistentArrayMap_get( + description, Keyword_create(String_create("implements")))); + + if (getType(implWrapper.get()) == persistentArrayMapType) { + PersistentArrayMap *map = + (PersistentArrayMap *)RT_unboxPtr(implWrapper.get()); + for (uword_t i = 0; i < map->count; i++) { + RTValue key = map->keys[i]; + RTValue value = map->values[i]; + if (getType(key) != symbolType && getType(key) != stringType) { + throwInternalInconsistencyException( + "Implements collection key is not a symbol or string."); + } + if (getType(value) != persistentArrayMapType) { + throwInternalInconsistencyException( + "Implements collection value is not a map."); + } + + retain(key); + String *ss = String_compactify(::toString(key)); + string sProtoName = String_c_str(ss); + Ptr_release(ss); + + // parse protocol methods + retain(value); + this->implements[sProtoName] = + parseIntrinsics(value, classData, this->type, true); + } } retain(root.get()); @@ -238,17 +287,6 @@ ClassDescription::ClassDescription(RTValue from, } } - 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")))); @@ -421,19 +459,30 @@ ClassDescription::parseIntrinsics(RTValue from, TemporaryClassData &classData, vector descriptions; PersistentVector *vec = (PersistentVector *)RT_unboxPtr(value); + uword_t count = vec->count; - uword_t count = vec->count; // Safe borrowed access. - - // PersistentVector_iterator does not consume vec. - PersistentVectorIterator it = PersistentVector_iterator(vec); + bool isSingleSignature = false; + if (count > 0) { + PersistentVectorNode *node = PersistentVector_nthBlock(vec, 0); + if (getType(node->array[0]) == keywordType) { + isSingleSignature = true; + } + } - for (uword_t j = 0; j < count; j++) { - RTValue intrinsicRaw = PersistentVector_iteratorGet(&it); - // IntrinsicDescription constructor consumes. Protect borrowed entry. - retain(intrinsicRaw); + if (isSingleSignature) { + retain(value); descriptions.push_back( - IntrinsicDescription(intrinsicRaw, classData, thisType, isInstance)); - PersistentVector_iteratorNext(&it); + IntrinsicDescription(value, classData, thisType, isInstance)); + } else { + // It's a vector of descriptions (maps or signature vectors) + PersistentVectorIterator it = PersistentVector_iterator(vec); + for (uword_t j = 0; j < count; j++) { + RTValue item = PersistentVector_iteratorGet(&it); + retain(item); + descriptions.push_back( + IntrinsicDescription(item, classData, thisType, isInstance)); + PersistentVector_iteratorNext(&it); + } } // toString consumes. Protect borrowed key for the map. @@ -492,8 +541,92 @@ IntrinsicDescription::IntrinsicDescription( this->isInstance = isInstance; this->returnsProvided = false; ConsumedValue root(from); + + if (getType(root.get()) == persistentVectorType) { + // It's a signature vector: [:this :any] + this->type = CallType::Call; + this->symbol = ""; // Abstract/dynamic + this->returnType = defaultReturnType; + + PersistentVector *argsVector = (PersistentVector *)RT_unboxPtr(root.get()); + uword_t argCnt = argsVector->count; + bool firstArgIsThis = false; + PersistentVectorIterator it = PersistentVector_iterator(argsVector); + for (uword_t j = 0; j < argCnt; j++) { + RTValue argRaw = PersistentVector_iteratorGet(&it); + retain(argRaw); + String *argStr = String_compactify(::toString(argRaw)); + string sArgKey = String_c_str(argStr); + Ptr_release(argStr); + + if (sArgKey == ":this") { + if (!isInstance) { + throwInternalInconsistencyException( + ":this used in a static function/intrinsic."); + } + if (j != 0) { + throwInternalInconsistencyException( + ":this must be the first argument."); + } + if (thisType.isDetermined() && + thisType.determinedType() == objectRootType) { + this->argTypes.push_back(ObjectTypeSet::dynamicType()); + } else { + this->argTypes.push_back(thisType); + } + firstArgIsThis = true; + } else if (sArgKey == ":any") { + this->argTypes.push_back(ObjectTypeSet::dynamicType()); + } else if (sArgKey == ":nil") { + this->argTypes.push_back(ObjectTypeSet(nilType)); + } else if (sArgKey == ":int") { + this->argTypes.push_back(ObjectTypeSet(integerType)); + } else if (sArgKey == ":bool") { + this->argTypes.push_back(ObjectTypeSet(booleanType)); + } else if (sArgKey == ":string") { + this->argTypes.push_back(ObjectTypeSet(stringType)); + } else if (sArgKey == ":symbol") { + this->argTypes.push_back(ObjectTypeSet(symbolType)); + } else if (sArgKey == ":keyword") { + this->argTypes.push_back(ObjectTypeSet(keywordType)); + } else if (sArgKey == ":double") { + this->argTypes.push_back(ObjectTypeSet(doubleType)); + } else { + if (classData.classesByName.find(sArgKey) == + classData.classesByName.end()) { + throwInternalInconsistencyException("Unknown arg type: " + sArgKey); + } + PersistentArrayMap *argClassMap = classData.classesByName[sArgKey]; + + // PersistentArrayMap_get consumes argClassMap. + Ptr_retain(argClassMap); + RTValue argTypeRaw = PersistentArrayMap_get( + argClassMap, Keyword_create(String_create("object-type"))); + ConsumedValue atWrapper(argTypeRaw); + + if (getType(atWrapper.get()) == integerType) { + objectType t = (objectType)RT_unboxInt32(atWrapper.get()); + if (t == objectRootType) { + this->argTypes.push_back(ObjectTypeSet::dynamicType()); + } else { + this->argTypes.push_back(ObjectTypeSet(t)); + } + } else { + this->argTypes.push_back(ObjectTypeSet(classType)); + } + } + PersistentVector_iteratorNext(&it); + } + if (isInstance && !firstArgIsThis) { + throwInternalInconsistencyException( + "Instance method signature missing :this argument"); + } + return; + } + if (getType(root.get()) != persistentArrayMapType) { - throwInternalInconsistencyException("Intrinsic description is not a map"); + throwInternalInconsistencyException( + "Intrinsic description is not a map or vector"); } PersistentArrayMap *map = (PersistentArrayMap *)RT_unboxPtr(root.get()); @@ -644,6 +777,18 @@ IntrinsicDescription::IntrinsicDescription( this->returnType = ObjectTypeSet::dynamicType(); } else if (sRetKey == ":nil") { this->returnType = ObjectTypeSet(nilType); + } else if (sRetKey == ":int") { + this->returnType = ObjectTypeSet(integerType); + } else if (sRetKey == ":bool") { + this->returnType = ObjectTypeSet(booleanType); + } else if (sRetKey == ":string") { + this->returnType = ObjectTypeSet(stringType); + } else if (sRetKey == ":symbol") { + this->returnType = ObjectTypeSet(symbolType); + } else if (sRetKey == ":keyword") { + this->returnType = ObjectTypeSet(keywordType); + } else if (sRetKey == ":double") { + this->returnType = ObjectTypeSet(doubleType); } else if (classData.classesByName.find(sRetKey) != classData.classesByName.end()) { PersistentArrayMap *retClassMap = classData.classesByName[sRetKey]; diff --git a/backend-v2/tools/EdnParser.h b/backend-v2/tools/EdnParser.h index bcac4659..860faeb1 100644 --- a/backend-v2/tools/EdnParser.h +++ b/backend-v2/tools/EdnParser.h @@ -55,10 +55,12 @@ class ClassDescription { const ObjectTypeSet &thisType); public: + bool isProtocol = false; ObjectTypeSet type; ::Class *extends; string name; string parentName; + vector parentNames; unordered_map staticFields; vector staticFieldValues; unordered_map staticFieldIndices; @@ -68,8 +70,10 @@ class ClassDescription { vector constructors; unordered_map> staticFns; unordered_map> instanceFns; + unordered_map>> + implements; - vector interfaces; + vector<::Class *> protocols; ClassDescription() = default; ClassDescription(RTValue from, TemporaryClassData &classData); diff --git a/frontend/resources/rt-classes.edn b/frontend/resources/rt-classes.edn index 4d0828a0..e017b7d1 100644 --- a/frontend/resources/rt-classes.edn +++ b/frontend/resources/rt-classes.edn @@ -8,9 +8,8 @@ {:object-type 17 :extends java.lang.Object} - -;; Assertion error is here just to enable assert. - java.lang.AssertionError + ;; Assertion error is here just to enable assert. + java.lang.AssertionError {:object-type 21 :extends java.lang.Object :constructor [{:args [java.lang.String] :type :call :symbol "Exception_createAssertionErrorWithMessage"}]} @@ -76,35 +75,35 @@ {: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}] + {: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 [: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}] + {: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 [:int :double] :type :intrinsic :symbol "Mul_ID" :returns :double} @@ -279,7 +278,7 @@ java.lang.String {:extends java.lang.Object :object-type 1 - :constructor [{:args [] :type :call :symbol "String_createEmpty" :returns java.lang.String}] + :constructor [{:args [] :type :call :symbol "String_createEmpty" :returns java.lang.String}] :static-fns {print [{:args [:any] :type :call :symbol "print" :returns :nil}]} :instance-fns @@ -297,23 +296,22 @@ 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 [: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}] - identical [{:args [:any :any] :type :call :symbol "identical_managed" :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}] + identical [{:args [:any :any] :type :call :symbol "identical_managed" :returns :bool}]}} clojure.lang.PersistentVector {:object-type 4 :extends java.lang.Object + :implements {clojure.lang.Counted {count [{:args [:this] :type :call :symbol "PersistentVector_count" :returns :int}]}} :instance-fns {asTransient [{:args [:this] :type :call :symbol "PersistentVector_transient" :returns clojure.lang.PersistentVector}] persistent [{:args [:this] :type :call :symbol "PersistentVector_persistent_BANG_" :returns clojure.lang.PersistentVector}] @@ -321,6 +319,25 @@ assoc [{:args [:this :int :any] :type :call :symbol "PersistentVector_assoc_BANG_" :returns clojure.lang.PersistentVector}] pop [{:args [:this] :type :call :symbol "PersistentVector_pop_BANG_" :returns clojure.lang.PersistentVector}]}} + +clojure.lang.PersistentList + {:object-type 3 + :extends java.lang.Object + :implements {clojure.lang.IPersistentList {} + clojure.lang.Sequential {} + clojure.lang.Counted {count [{:args [:this] :type :call :symbol "PersistentList_count" :returns :int}]} + clojure.lang.IPersistentStack {peek [{:args [:this] :type :call :symbol "PersistentList_first" :returns :any}] + pop [{:args [:this] :type :call :symbol "PersistentList_pop" :returns clojure.lang.PersistentList}]} + clojure.lang.IPersistentCollection {count [{:args [:this] :type :call :symbol "PersistentList_count" :returns :int}] + cons [{:args [:this :any] :type :call :symbol "PersistentList_conj" :returns clojure.lang.PersistentList}] + equiv [{:args [:this :any] :type :call :symbol "PersistentList_equals_managed" :returns :bool}] + empty [{:args [:this] :type :call :symbol "PersistentList_empty" :returns clojure.lang.PersistentList}]} + clojure.lang.ISeq {first [{:args [:this] :type :call :symbol "PersistentList_first" :returns :any}] + next [{:args [:this] :type :call :symbol "PersistentList_next" :returns :any}] + more [{:args [:this] :type :call :symbol "PersistentList_identity" :returns clojure.lang.PersistentList}] + cons [{:args [:this :any] :type :call :symbol "PersistentList_conj" :returns clojure.lang.PersistentList}]} + clojure.lang.Sequable {seq [{:args [:this] :type :call :symbol "PersistentList_identity" :returns clojure.lang.PersistentList}]}}} + clojure.lang.RT {:static-fns {conj [{:args [clojure.lang.PersistentVector :any] :type :call :symbol "PersistentVector_conj" :returns clojure.lang.PersistentVector}] diff --git a/frontend/resources/rt-protocols.edn b/frontend/resources/rt-protocols.edn index ba899dc3..5c24a2f0 100644 --- a/frontend/resources/rt-protocols.edn +++ b/frontend/resources/rt-protocols.edn @@ -44,6 +44,13 @@ :instance-fns {dropFirst [:this] reduce [:this :any]}} + clojure.lang.IChunkedSeq + {:extends [clojure.lang.ISeq clojure.lang.Sequential] + :is-interface true + :instance-fns {chunkedFirst [:this] + chunkedNext [:this] + chunkedMore [:this]}} + clojure.lang.Sequable {:is-interface true :instance-fns {seq [:this]}} @@ -79,39 +86,39 @@ clojure.lang.IExceptionInfo {:is-interface true :instance-fns {getData [:this]}} - + java.util.concurrent.Callable {:is-interface true :instance-fns {call [:this]}} - + java.lang.Runnable {:is-interface true :instance-fns {run [:this]}} - - clojure.lang.IFn + + clojure.lang.IFn {:is-interface true :extends [java.util.concurrent.Callable java.lang.Runnable] - :instance-fns {invoke [[:this] [:this :any] [:this :any :any] [:this :any :any :any] [:this :any :any :any :any] - [:this :any :any :any :any :any] [:this :any :any :any :any :any :any] - [:this :any :any :any :any :any :any :any] [:this :any :any :any :any :any :any :any :any] - [:this :any :any :any :any :any :any :any :any :any] [:this :any :any :any :any :any :any :any :any :any :any] - [:this :any :any :any :any :any :any :any :any :any :any :any] - [:this :any :any :any :any :any :any :any :any :any :any :any :any] - [:this :any :any :any :any :any :any :any :any :any :any :any :any :any] - [:this :any :any :any :any :any :any :any :any :any :any :any :any :any :any] - [:this :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any] - [:this :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any] - [:this :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any] - [:this :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any] - [:this :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any] - [:this :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any]] + :instance-fns {invoke [[:this] [:this :any] [:this :any :any] [:this :any :any :any] [:this :any :any :any :any] + [:this :any :any :any :any :any] [:this :any :any :any :any :any :any] + [:this :any :any :any :any :any :any :any] [:this :any :any :any :any :any :any :any :any] + [:this :any :any :any :any :any :any :any :any :any] [:this :any :any :any :any :any :any :any :any :any :any] + [:this :any :any :any :any :any :any :any :any :any :any :any] + [:this :any :any :any :any :any :any :any :any :any :any :any :any] + [:this :any :any :any :any :any :any :any :any :any :any :any :any :any] + [:this :any :any :any :any :any :any :any :any :any :any :any :any :any :any] + [:this :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any] + [:this :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any] + [:this :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any] + [:this :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any] + [:this :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any] + [:this :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any :any]] applyTo [:this :any]}} clojure.lang.IHashEq {:is-interface true :instance-fns {hasheq [:this]}} - clojure.lang.IKVReduce + clojure.lang.IKVReduce {:is-interface true :instance-fns {kvreduce [:this :any :any]}} @@ -122,7 +129,7 @@ clojure.lang.ILookup {:is-interface true :instance-fns {valAt [[:this :any] [:this :any :any]]}} - + clojure.lang.ILookupSite {:is-interface true :instance-fns {fault [:this :any]}} diff --git a/tests/invoke-nonexistent.clj b/tests/invoke-nonexistent.clj new file mode 100644 index 00000000..c8672ad3 --- /dev/null +++ b/tests/invoke-nonexistent.clj @@ -0,0 +1,3 @@ +(def zumba) +(zumba 1) +