diff --git a/backend-v2/bridge/Exceptions.cpp b/backend-v2/bridge/Exceptions.cpp index 39cd6038..160a9aa1 100644 --- a/backend-v2/bridge/Exceptions.cpp +++ b/backend-v2/bridge/Exceptions.cpp @@ -687,6 +687,17 @@ throwNoMatchingOverloadException_C(const char *className, RT_boxPtr(::String_createDynamicStr(ss.str().c_str())), RT_boxNil()); } +extern "C" [[noreturn]] void +throwNoMatchingConstructorException_C(const char *className, + int arity) { + std::stringstream ss; + ss << "No matching constructor found for " << className << " with arity " << arity; + throw rt::LanguageException( + "NoMatchingConstructorException", + RT_boxPtr(::String_createDynamicStr(ss.str().c_str())), RT_boxNil()); +} + + extern "C" void throwLanguageException_C(const char *name, RTValue message, RTValue payload) { throw rt::LanguageException(name, message, payload); diff --git a/backend-v2/bridge/Exceptions.h b/backend-v2/bridge/Exceptions.h index 73aa2b63..3164827b 100644 --- a/backend-v2/bridge/Exceptions.h +++ b/backend-v2/bridge/Exceptions.h @@ -112,6 +112,10 @@ extern "C" [[noreturn]] void throwNoMatchingOverloadException_C(const char *className, const char *methodName); +extern "C" [[noreturn]] void +throwNoMatchingConstructorException_C(const char *className, + int arity); + extern "C" String *exceptionToString_C(void *exception); extern "C" void *createException_C(const char *className, String *message, RTValue payload); diff --git a/backend-v2/codegen/CodeGen.cpp b/backend-v2/codegen/CodeGen.cpp index a11ee640..90a5436c 100644 --- a/backend-v2/codegen/CodeGen.cpp +++ b/backend-v2/codegen/CodeGen.cpp @@ -181,8 +181,15 @@ llvm::Function *CodeGen::generateBaselineMethod( [](unsigned char c) { return !std::isalnum(c); }, '_'); funcName += sanitized + "_"; } + funcName += "arity_" + std::to_string(method.fixedarity()) + "_"; + if (method.isvariadic()) { + funcName += "variadic_"; + } funcName += generateRandomHex(16); + std::string savedSuggestedFunctionName = suggestedFunctionName; + suggestedFunctionName = ""; + // Create the function with baseline signature: (Frame*, Arg0, Arg1, Arg2, // Arg3, Arg4) -> RTValue Function *F = @@ -351,6 +358,7 @@ llvm::Function *CodeGen::generateBaselineMethod( variableTypesBindingsStack.pop(); verifyFunction(*F); + suggestedFunctionName = savedSuggestedFunctionName; return F; } diff --git a/backend-v2/codegen/invoke/InvokeManager.cpp b/backend-v2/codegen/invoke/InvokeManager.cpp index 07355b0e..aa5cee48 100644 --- a/backend-v2/codegen/invoke/InvokeManager.cpp +++ b/backend-v2/codegen/invoke/InvokeManager.cpp @@ -1,7 +1,7 @@ #include "InvokeManager.h" -#include "../../tools/RandomID.h" #include "../../bridge/Exceptions.h" #include "../../tools/EdnParser.h" +#include "../../tools/RandomID.h" #include "../../types/ConstantBigInteger.h" #include "../../types/ConstantBool.h" #include "../../types/ConstantDouble.h" @@ -343,9 +343,9 @@ TypedValue InvokeManager::invokeRuntime( } } - return TypedValue( - retValType ? *retValType : ObjectTypeSet::all(), - invokeRaw(fname, functionType, argVals, guard, consumesArgs, extraCleanup)); + return TypedValue(retValType ? *retValType : ObjectTypeSet::all(), + invokeRaw(fname, functionType, argVals, guard, consumesArgs, + extraCleanup)); } TypedValue InvokeManager::generateRawMethodCall( @@ -397,7 +397,8 @@ TypedValue InvokeManager::generateRawMethodCall( ? static_cast(&*currentFn->arg_begin()) : static_cast( ConstantPointerNull::get(cast(types.ptrTy))); - builder.CreateStore(currentFrame, types.getFrameLeafFramePtr(builder, framePtr)); + builder.CreateStore(currentFrame, + types.getFrameLeafFramePtr(builder, framePtr)); builder.CreateStore(methodPtr, types.getFrameMethodPtr(builder, framePtr)); builder.CreateStore(valueEncoder.box(self).value, types.getFrameSelfPtr(builder, framePtr)); @@ -478,8 +479,9 @@ TypedValue InvokeManager::generateRawMethodCall( return TypedValue(ObjectTypeSet::dynamicType(), res); } -llvm::Value *InvokeManager::generateICLookup( - llvm::Value *currentVal, size_t argCount, CleanupChainGuard *guard) { +llvm::Value *InvokeManager::generateICLookup(llvm::Value *currentVal, + size_t argCount, + CleanupChainGuard *guard) { auto &TheContext = theModule.getContext(); llvm::Function *currentFn = builder.GetInsertBlock()->getParent(); diff --git a/backend-v2/codegen/invoke/InvokeManager_InstanceCall.cpp b/backend-v2/codegen/invoke/InvokeManager_InstanceCall.cpp index a613a100..9780cc94 100644 --- a/backend-v2/codegen/invoke/InvokeManager_InstanceCall.cpp +++ b/backend-v2/codegen/invoke/InvokeManager_InstanceCall.cpp @@ -84,7 +84,7 @@ TypedValue InvokeManager::generateDynamicInstanceCall( // 1. Get current instance type TypedValue boxedInstance = valueEncoder.box(instance); FunctionType *getTypeSig = - FunctionType::get(types.i64Ty, {types.RT_valueTy}, false); + FunctionType::get(types.wordTy, {types.RT_valueTy}, false); FunctionCallee getTypeFunc = theModule.getOrInsertFunction("getType", getTypeSig); Value *currentTypeIdent = @@ -380,7 +380,7 @@ TypedValue InvokeManager::generateDeterminedInstanceCall( Value *targetVal = ConstantInt::get(this->types.i32Ty, (uint32_t)target); Value *isType = - this->builder.CreateICmpEQ(argRuntimeTypes[i], targetVal); + this->builder.CreateICmpEQ(this->builder.CreateTrunc(argRuntimeTypes[i], this->types.i32Ty), targetVal); if (match) { match = this->builder.CreateAnd(match, isType); diff --git a/backend-v2/codegen/invoke/InvokeManager_Math.cpp b/backend-v2/codegen/invoke/InvokeManager_Math.cpp index 6711ed50..b64c157d 100644 --- a/backend-v2/codegen/invoke/InvokeManager_Math.cpp +++ b/backend-v2/codegen/invoke/InvokeManager_Math.cpp @@ -483,11 +483,16 @@ void registerMathIntrinsics(InvokeManager &mgr) { const std::string &errorMessage) { return [&mgr, &theModule, &types, llvmIntrinsic, errorMessage]( llvm::IRBuilder<> &b, std::vector args) { - Function *fn = Intrinsic::getOrInsertDeclaration( - &theModule, - llvmIntrinsic == "sadd" ? Intrinsic::sadd_with_overflow - : Intrinsic::ssub_with_overflow, - {b.getInt32Ty()}); + Intrinsic::ID id = Intrinsic::not_intrinsic; + if (llvmIntrinsic == "sadd") + id = Intrinsic::sadd_with_overflow; + else if (llvmIntrinsic == "ssub") + id = Intrinsic::ssub_with_overflow; + else if (llvmIntrinsic == "smul") + id = Intrinsic::smul_with_overflow; + + Function *fn = + Intrinsic::getOrInsertDeclaration(&theModule, id, {b.getInt32Ty()}); Value *resStruct = b.CreateCall(fn, args); Value *res = b.CreateExtractValue(resStruct, {0}); Value *overflow = b.CreateExtractValue(resStruct, {1}); @@ -524,9 +529,7 @@ void registerMathIntrinsics(InvokeManager &mgr) { intrinsics["FMul"] = [](auto &b, auto args) { return b.CreateFMul(args[0], args[1]); }; - intrinsics["Mul"] = [](auto &b, auto args) { - return b.CreateMul(args[0], args[1]); - }; + intrinsics["Mul"] = createCheckedOp("smul", "Integer overflow"); intrinsics["FDiv"] = [](auto &b, auto args) { return b.CreateFDiv(args[0], args[1]); }; diff --git a/backend-v2/codegen/ops/NewNode.cpp b/backend-v2/codegen/ops/NewNode.cpp index 07e9149e..681b0530 100644 --- a/backend-v2/codegen/ops/NewNode.cpp +++ b/backend-v2/codegen/ops/NewNode.cpp @@ -46,16 +46,15 @@ TypedValue CodeGen::codegen(const Node &node, const NewNode &subnode, "Class " + className + " does not have compiler metadata", node); } - // 3. Find matching constructor by arity - const IntrinsicDescription *bestMatch = nullptr; + // 3. Find matching constructors by arity + std::vector versions; for (const auto &ctor : ext->constructors) { if ((int32_t)ctor.argTypes.size() == subnode.args_size()) { - bestMatch = &ctor; - break; + versions.push_back(&ctor); } } - if (!bestMatch) { + if (versions.empty()) { throwCodeGenerationException("Class " + className + " does not have a constructor of arity " + to_string(subnode.args_size()), @@ -64,24 +63,136 @@ TypedValue CodeGen::codegen(const Node &node, const NewNode &subnode, // 4. Codegen arguments std::vector args; + bool allDetermined = true; for (int i = 0; i < subnode.args_size(); i++) { auto t = codegen(subnode.args(i), ObjectTypeSet::all()); + if (!t.type.isDetermined()) + allDetermined = false; args.push_back(t); guard.push(t); } - std::vector unboxedArgs; - for (size_t i = 0; i < args.size(); i++) { - if (!bestMatch->argTypes[i].isBoxedType()) { - unboxedArgs.push_back(this->valueEncoder.unbox(args[i])); - } else { - unboxedArgs.push_back(this->valueEncoder.box(args[i])); + const IntrinsicDescription *bestMatch = nullptr; + if (allDetermined) { + // Exact match trial + for (const auto *ctor : versions) { + bool match = true; + for (size_t i = 0; i < args.size(); i++) { + if (args[i].type.restriction(ctor->argTypes[i]).isEmpty()) { + match = false; + break; + } + } + if (match) { + bestMatch = ctor; + break; + } + } + } + + if (bestMatch) { + std::vector unboxedArgs; + for (size_t i = 0; i < args.size(); i++) { + if (!bestMatch->argTypes[i].isBoxedType()) { + unboxedArgs.push_back(this->valueEncoder.unbox(args[i])); + } else { + unboxedArgs.push_back(this->valueEncoder.box(args[i])); + } + } + return this->invokeManager.generateIntrinsic(*bestMatch, unboxedArgs, + &guard); + } + + // No exact compile-time match -> Dynamic Dispatch or specialized trials + Function *currentFn = this->Builder.GetInsertBlock()->getParent(); + BasicBlock *mergeBB = + BasicBlock::Create(this->TheContext, "new_dispatch_merge", currentFn); + + struct DispatchCase { + BasicBlock *block; + TypedValue value; + }; + std::vector cases; + std::vector argRuntimeTypes(args.size(), nullptr); + + for (const auto *fid : versions) { + BasicBlock *thenBB = + BasicBlock::Create(this->TheContext, "new_specialised", currentFn); + BasicBlock *nextBB = + BasicBlock::Create(this->TheContext, "new_specialised_next", currentFn); + + Value *match = nullptr; + for (size_t i = 0; i < args.size(); i++) { + if (fid->argTypes[i].isDetermined()) { + if (!argRuntimeTypes[i]) { + TypedValue boxed = this->valueEncoder.box(args[i]); + FunctionType *getTypeSig = FunctionType::get( + this->types.wordTy, {this->types.RT_valueTy}, false); + argRuntimeTypes[i] = this->invokeManager.invokeRaw( + "getType", getTypeSig, {boxed.value}, &guard, true); + } + + uint32_t targetTypeID = fid->argTypes[i].determinedType(); + Value *targetVal = + ConstantInt::get(this->types.i32Ty, (uint32_t)targetTypeID); + Value *isType = this->Builder.CreateICmpEQ( + this->Builder.CreateTrunc(argRuntimeTypes[i], this->types.i32Ty), + targetVal); + + if (match) + match = this->Builder.CreateAnd(match, isType); + else + match = isType; + } + } + + if (!match) + match = this->Builder.getTrue(); + + this->Builder.CreateCondBr(match, thenBB, nextBB); + this->Builder.SetInsertPoint(thenBB); + + std::vector specializedArgs; + for (size_t i = 0; i < args.size(); i++) { + if (fid->argTypes[i].isDetermined()) { + TypedValue valToUnbox = args[i]; + if (valToUnbox.type.isBoxedType()) { + valToUnbox = TypedValue(fid->argTypes[i].boxed(), valToUnbox.value); + } + llvm::Value *unboxedVal = this->valueEncoder.unbox(valToUnbox).value; + specializedArgs.push_back(TypedValue(fid->argTypes[i], unboxedVal)); + } else { + specializedArgs.push_back(this->valueEncoder.box(args[i])); + } } + + TypedValue res = + this->invokeManager.generateIntrinsic(*fid, specializedArgs, &guard); + TypedValue boxedRes = this->valueEncoder.box(res); + cases.push_back({this->Builder.GetInsertBlock(), boxedRes}); + this->Builder.CreateBr(mergeBB); + this->Builder.SetInsertPoint(nextBB); } - return this->invokeManager.generateIntrinsic(*bestMatch, unboxedArgs, &guard); + // Fallback -> No matching constructor found at runtime + Value *clsName = this->Builder.CreateGlobalString(className, "new_fail_class"); + FunctionType *fnTy = FunctionType::get( + this->types.voidTy, {this->types.ptrTy, this->types.i32Ty}, false); + this->invokeManager.invokeRaw( + "throwNoMatchingConstructorException_C", fnTy, + {clsName, ConstantInt::get(this->types.i32Ty, subnode.args_size())}); + this->Builder.CreateUnreachable(); + + this->Builder.SetInsertPoint(mergeBB); + PHINode *phi = Builder.CreatePHI(types.RT_valueTy, cases.size(), "new_phi"); + for (auto &c : cases) { + phi->addIncoming(c.value.value, c.block); + } + + return TypedValue(ObjectTypeSet((objectType)cls->registerId), phi); } + ObjectTypeSet CodeGen::getType(const Node &node, const NewNode &subnode, const ObjectTypeSet &typeRestrictions) { auto classTypeSet = getType(subnode.class_(), ObjectTypeSet::all()); diff --git a/backend-v2/codegen/ops/StaticCallNode.cpp b/backend-v2/codegen/ops/StaticCallNode.cpp index ae144450..6c058971 100644 --- a/backend-v2/codegen/ops/StaticCallNode.cpp +++ b/backend-v2/codegen/ops/StaticCallNode.cpp @@ -167,19 +167,18 @@ TypedValue CodeGen::codegen(const Node &node, const StaticCallNode &subnode, if (fid->argTypes[i].isDetermined()) { if (!argRuntimeTypes[i]) { TypedValue boxed = this->valueEncoder.box(args[i]); - ObjectTypeSet retType(integerType, false); - std::vector pArgTypes = {ObjectTypeSet::dynamicType()}; - std::vector pArgVals = {boxed}; - TypedValue typeVal = this->invokeManager.invokeRuntime( - "getType", &retType, pArgTypes, pArgVals); - argRuntimeTypes[i] = typeVal.value; + FunctionType *getTypeSig = FunctionType::get( + this->types.wordTy, {this->types.RT_valueTy}, false); + argRuntimeTypes[i] = this->invokeManager.invokeRaw( + "getType", getTypeSig, {boxed.value}, &guard, true); } - uint32_t target = fid->argTypes[i].determinedType(); + uint32_t targetID = fid->argTypes[i].determinedType(); Value *targetVal = - ConstantInt::get(this->types.i32Ty, (uint32_t)target); - Value *isType = - this->Builder.CreateICmpEQ(argRuntimeTypes[i], targetVal); + ConstantInt::get(this->types.i32Ty, (uint32_t)targetID); + Value *isType = this->Builder.CreateICmpEQ( + this->Builder.CreateTrunc(argRuntimeTypes[i], this->types.i32Ty), + targetVal); if (match) { match = this->Builder.CreateAnd(match, isType); @@ -199,29 +198,11 @@ TypedValue CodeGen::codegen(const Node &node, const StaticCallNode &subnode, std::vector specializedArgs; for (size_t i = 0; i < args.size(); i++) { if (fid->argTypes[i].isDetermined()) { - uint32_t target = fid->argTypes[i].determinedType(); - llvm::Value *unboxedVal = nullptr; - if (target == integerType) - unboxedVal = this->valueEncoder.unboxInt32(args[i]).value; - else if (target == doubleType) - unboxedVal = this->valueEncoder.unboxDouble(args[i]).value; - else if (target == booleanType) - unboxedVal = this->valueEncoder.unboxBool(args[i]).value; - else if (target == stringType || target == persistentListType || - target == persistentVectorType || target == bigIntegerType || - target == ratioType || target == keywordType || - target == symbolType || target == functionType || - target == classType || target == persistentArrayMapType || - target == varType || target == exceptionType || - target == bridgedObjectType) { - unboxedVal = this->valueEncoder.unboxPointer(args[i]).value; - } else if (target == nilType) { - unboxedVal = args[i].value; - } else { - unboxedVal = this->valueEncoder.unbox(args[i]).value; + TypedValue valToUnbox = args[i]; + if (valToUnbox.type.isBoxedType()) { + valToUnbox = TypedValue(fid->argTypes[i].boxed(), valToUnbox.value); } - // CRITICAL: Set the verified type so InvokeManager doesn't complain - specializedArgs.push_back(TypedValue(fid->argTypes[i], unboxedVal)); + specializedArgs.push_back(this->valueEncoder.unbox(valToUnbox)); } else { specializedArgs.push_back(this->valueEncoder.box(args[i])); } diff --git a/backend-v2/jit/JITEngine.cpp b/backend-v2/jit/JITEngine.cpp index cd590f22..79fa72c1 100644 --- a/backend-v2/jit/JITEngine.cpp +++ b/backend-v2/jit/JITEngine.cpp @@ -380,6 +380,8 @@ void JITEngine::registerRuntimeSymbols() { runtimeSymbols.insert(absoluteSymbol("equals", (void *)::equals)); runtimeSymbols.insert(absoluteSymbol("hash", (void *)::hash)); runtimeSymbols.insert(absoluteSymbol("toString", (void *)toString)); + runtimeSymbols.insert( + absoluteSymbol("identical_managed", (void *)identical_managed)); runtimeSymbols.insert( absoluteSymbol("equals_managed", (void *)equals_managed)); diff --git a/backend-v2/main.cpp b/backend-v2/main.cpp index 4048a8bc..025f043e 100644 --- a/backend-v2/main.cpp +++ b/backend-v2/main.cpp @@ -33,20 +33,20 @@ using namespace std; using namespace llvm; +#include + class ExecutionTimer { std::string name; - std::chrono::high_resolution_clock::time_point start; + std::chrono::steady_clock::time_point start; public: ExecutionTimer(const std::string &n) - : name(n), start(std::chrono::high_resolution_clock::now()) {} + : name(n), start(std::chrono::steady_clock::now()) {} ~ExecutionTimer() { - auto end = std::chrono::high_resolution_clock::now(); - auto duration = - std::chrono::duration_cast(end - start) - .count(); - std::cout << "[TIMER] " << name << " took " << duration << " ms" - << std::endl; + auto end = std::chrono::steady_clock::now(); + std::chrono::duration duration = end - start; + std::cout << "[TIMER] " << name << " took " << std::fixed + << std::setprecision(3) << duration.count() << " ms" << std::endl; } }; @@ -130,11 +130,15 @@ int main(int argc, char *argv[]) { cout << "Compiling root..." << endl; for (int j = 0; j < astRoot.nodes_size(); j++) { std::string moduleName = "__repl__" + std::to_string(j); - ExecutionTimer t("Executing " + moduleName); auto topLevelNode = astRoot.nodes(j); cout << "=============================" << endl; - cout << "Compiling!!!" << endl; - auto res = engine.compileAST(topLevelNode, moduleName).get(); + + rt::JITResult res; + { + ExecutionTimer t("Compiling " + moduleName); + cout << "Compiling!!!" << endl; + res = engine.compileAST(topLevelNode, moduleName).get(); + } if (!res.optimizedIR.empty()) { cout << "\n=== Optimized LLVM IR for: '" << moduleName << "' ===\n"; @@ -142,14 +146,17 @@ int main(int argc, char *argv[]) { cout << "===============================================\n" << endl; } - RTValue whaat = res.address.toPtr()(); - String *s = toString(whaat); - s = String_compactify(s); + { + ExecutionTimer t("Executing " + moduleName); + RTValue whaat = res.address.toPtr()(); + String *s = toString(whaat); + s = String_compactify(s); - cout << "========== Result ==========" << endl; - cout << std::string(String_c_str(s)) << endl; - cout << "========== /Result ==========" << endl; - Ptr_release(s); + cout << "========== Result ==========" << endl; + cout << std::string(String_c_str(s)) << endl; + cout << "========== /Result ==========" << endl; + Ptr_release(s); + } } retVal = 0; } catch (const rt::LanguageException &e) { diff --git a/backend-v2/runtime/CMakeLists.txt b/backend-v2/runtime/CMakeLists.txt index 46f90919..9f533a38 100644 --- a/backend-v2/runtime/CMakeLists.txt +++ b/backend-v2/runtime/CMakeLists.txt @@ -39,7 +39,8 @@ set(SOURCES Numbers.c Exception.c BridgedObject.c - Ebr.c) + Ebr.c + StringBuilder.c) add_library(runtime STATIC ${SOURCES}) target_compile_options(runtime PRIVATE $<$:-femulated-tls>) @@ -188,6 +189,7 @@ set(TEST_MODULES RefcountPerf_test Numbers_test Exception_test + StringBuilder_test ) foreach(TEST_MODULE ${TEST_MODULES}) diff --git a/backend-v2/runtime/Function.c b/backend-v2/runtime/Function.c index a13707ab..ad8adc6c 100644 --- a/backend-v2/runtime/Function.c +++ b/backend-v2/runtime/Function.c @@ -1,18 +1,18 @@ -#include "Object.h" #include "Function.h" -#include "Integer.h" +#include "Exceptions.h" #include "Hash.h" +#include "Integer.h" +#include "Object.h" +#include "PersistentList.h" #include "RTValue.h" #include "String.h" -#include "Exceptions.h" -#include "PersistentList.h" #include #include #include - /* mem done */ -ClojureFunction* Function_create(uword_t methodCount, uword_t maxArity, bool once) { +ClojureFunction *Function_create(uword_t methodCount, uword_t maxArity, + bool once) { size_t size = sizeof(ClojureFunction) + methodCount * sizeof(FunctionMethod); ClojureFunction *self = (ClojureFunction *)allocate(size); memset(self, 0, size); @@ -24,20 +24,25 @@ ClojureFunction* Function_create(uword_t methodCount, uword_t maxArity, bool onc return self; } -/* outside refcount system (w.r.t. self) and closedOvers (they need to be retained already when this fun is called) */ -void Function_fillMethod(ClojureFunction *self, uword_t position, uword_t index, uword_t fixedArity, bool isVariadic, void *implementation, char *loopId, word_t closedOversCount, ...) { - FunctionMethod * method = self->methods + position; +/* outside refcount system (w.r.t. self) and closedOvers (they need to be + * retained already when this fun is called) */ +void Function_fillMethod(ClojureFunction *self, uword_t position, uword_t index, + uword_t fixedArity, bool isVariadic, + void *implementation, char *loopId, + word_t closedOversCount, ...) { + FunctionMethod *method = self->methods + position; method->fixedArity = fixedArity; method->isVariadic = isVariadic; method->baselineImplementation = implementation; method->loopId = loopId; method->index = index; method->closedOversCount = closedOversCount; - if(closedOversCount > 0) method->closedOvers = allocate(closedOversCount * sizeof(RTValue)); + if (closedOversCount > 0) + method->closedOvers = allocate(closedOversCount * sizeof(RTValue)); va_list args; va_start(args, closedOversCount); - for(word_t i=0; iclosedOvers[i] = closedOver; } va_end(args); @@ -47,7 +52,8 @@ void Function_fillMethod(ClojureFunction *self, uword_t position, uword_t index, bool Function_validCallWithArgCount(ClojureFunction *self, uword_t argCount) { if (argCount <= self->maxArity) { for (uword_t i = 0; i < self->methodCount; ++i) { - if (self->methods[i].fixedArity == argCount && !self->methods[i].isVariadic) { + if (self->methods[i].fixedArity == argCount && + !self->methods[i].isVariadic) { return true; } } @@ -78,9 +84,10 @@ String *Function_toString(ClojureFunction *self) { for (uword_t i = 0; i < self->methodCount; ++i) { FunctionMethod *m = &self->methods[i]; pos += snprintf(buf + pos, sizeof(buf) - pos, - "%s[arity %d, %s, closed-overs %d, impl %p]", (i > 0 ? ", " : ""), - (int)m->fixedArity, (m->isVariadic ? "var" : "fixed"), - (int)m->closedOversCount, m->baselineImplementation); + "%s[arity %d, %s, closed-overs %d, impl %p]", + (i > 0 ? ", " : ""), (int)m->fixedArity, + (m->isVariadic ? "var" : "fixed"), (int)m->closedOversCount, + m->baselineImplementation); if (pos >= (int)sizeof(buf) - 64) break; // Avoid overflow or very long strings } @@ -93,27 +100,32 @@ String *Function_toString(ClojureFunction *self) { void Function_cleanupOnce(ClojureFunction *self) { assert(!self->executed && "Function with :once meta executed more than once"); self->executed = true; - for(uword_t i=0; i < self->methodCount; i++) { + for (uword_t i = 0; i < self->methodCount; i++) { FunctionMethod *method = &(self->methods[i]); - for(uword_t j=0; j < method->closedOversCount; j++) release(method->closedOvers[j]); - if(method->closedOversCount) deallocate(method->closedOvers); + for (uword_t j = 0; j < method->closedOversCount; j++) + release(method->closedOvers[j]); + if (method->closedOversCount) + deallocate(method->closedOvers); } } - /* outside refcount system */ void Function_destroy(ClojureFunction *self) { - if(self->once && self->executed) return; + if (self->once && self->executed) + return; - for(uword_t i=0; i < self->methodCount; i++) { + for (uword_t i = 0; i < self->methodCount; i++) { FunctionMethod *method = &(self->methods[i]); - for(uword_t j=0; j < method->closedOversCount; j++) release(method->closedOvers[j]); - if(method->closedOversCount) deallocate(method->closedOvers); + for (uword_t j = 0; j < method->closedOversCount; j++) + release(method->closedOvers[j]); + if (method->closedOversCount) + deallocate(method->closedOvers); } - } RTValue RT_invokeDynamic(RTValue funObj, RTValue *args, int32_t argCount) { - (void)funObj; (void)args; (void)argCount; + (void)funObj; + (void)args; + (void)argCount; fprintf(stderr, "RT_invokeDynamic: Not yet implemented\n"); abort(); } @@ -127,14 +139,16 @@ FunctionMethod *Function_extractMethod(RTValue funObj, uword_t argCount) { // 1. Exact match for (uword_t i = 0; i < self->methodCount; ++i) { - if (!self->methods[i].isVariadic && self->methods[i].fixedArity == argCount) { + if (!self->methods[i].isVariadic && + self->methods[i].fixedArity == argCount) { return &self->methods[i]; } } // 2. Variadic match for (uword_t i = 0; i < self->methodCount; ++i) { - if (self->methods[i].isVariadic && argCount >= self->methods[i].fixedArity) { + if (self->methods[i].isVariadic && + argCount >= self->methods[i].fixedArity) { return &self->methods[i]; } } @@ -143,52 +157,81 @@ FunctionMethod *Function_extractMethod(RTValue funObj, uword_t argCount) { } RTValue RT_packVariadic(uword_t argCount, RTValue *args, uword_t fixedArity) { - if (argCount <= fixedArity) return RT_boxNil(); - return RT_createListFromArray((int32_t)(argCount - fixedArity), args + fixedArity); + if (argCount <= fixedArity) + return RT_boxNil(); + return RT_createListFromArray((int32_t)(argCount - fixedArity), + args + fixedArity); } /* --- Universal Callable Bridges --- */ -static RTValue Baseline_Keyword_Invoke_1(Frame *frame, RTValue arg0, RTValue a1, RTValue a2, RTValue a3, RTValue a4) { - return Keyword_invoke(frame->self, arg0, RT_boxNil()); +static RTValue Baseline_Keyword_Invoke_1(Frame *frame, RTValue arg0, RTValue a1, + RTValue a2, RTValue a3, RTValue a4) { + retain(frame->self); + return Keyword_invoke(frame->self, arg0, RT_boxNil()); } -static RTValue Baseline_Keyword_Invoke_2(Frame *frame, RTValue arg0, RTValue arg1, RTValue a2, RTValue a3, RTValue a4) { - return Keyword_invoke(frame->self, arg0, arg1); +static RTValue Baseline_Keyword_Invoke_2(Frame *frame, RTValue arg0, + RTValue arg1, RTValue a2, RTValue a3, + RTValue a4) { + retain(frame->self); + return Keyword_invoke(frame->self, arg0, arg1); } -static RTValue Baseline_Map_Invoke_1(Frame *frame, RTValue arg0, RTValue a1, RTValue a2, RTValue a3, RTValue a4) { - return PersistentArrayMap_dynamic_get(frame->self, arg0); +static RTValue Baseline_Map_Invoke_1(Frame *frame, RTValue arg0, RTValue a1, + RTValue a2, RTValue a3, RTValue a4) { + retain(frame->self); + return PersistentArrayMap_dynamic_get(frame->self, arg0); } -static RTValue Baseline_Vector_Invoke_1(Frame *frame, RTValue arg0, RTValue a1, RTValue a2, RTValue a3, RTValue a4) { - return PersistentVector_dynamic_nth((PersistentVector *)RT_unboxPtr(frame->self), arg0); +static RTValue Baseline_Vector_Invoke_1(Frame *frame, RTValue arg0, RTValue a1, + RTValue a2, RTValue a3, RTValue a4) { + retain(frame->self); + return PersistentVector_dynamic_nth( + (PersistentVector *)RT_unboxPtr(frame->self), arg0); } // Global method descriptors for non-function callables -struct FunctionMethod Global_Keyword_Method_1 = { .fixedArity = 1, .isVariadic = false, .baselineImplementation = Baseline_Keyword_Invoke_1 }; -struct FunctionMethod Global_Keyword_Method_2 = { .fixedArity = 2, .isVariadic = false, .baselineImplementation = Baseline_Keyword_Invoke_2 }; -struct FunctionMethod Global_Map_Method_1 = { .fixedArity = 1, .isVariadic = false, .baselineImplementation = Baseline_Map_Invoke_1 }; -struct FunctionMethod Global_Vector_Method_1 = { .fixedArity = 1, .isVariadic = false, .baselineImplementation = Baseline_Vector_Invoke_1 }; +struct FunctionMethod Global_Keyword_Method_1 = {.fixedArity = 1, + .isVariadic = false, + .baselineImplementation = + Baseline_Keyword_Invoke_1}; +struct FunctionMethod Global_Keyword_Method_2 = {.fixedArity = 2, + .isVariadic = false, + .baselineImplementation = + Baseline_Keyword_Invoke_2}; +struct FunctionMethod Global_Map_Method_1 = {.fixedArity = 1, + .isVariadic = false, + .baselineImplementation = + Baseline_Map_Invoke_1}; +struct FunctionMethod Global_Vector_Method_1 = {.fixedArity = 1, + .isVariadic = false, + .baselineImplementation = + Baseline_Vector_Invoke_1}; struct FunctionMethod *RT_updateICSlot(void *slot, RTValue currentVal, - uint64_t argCount) { + uint64_t argCount) { objectType type = getType(currentVal); struct FunctionMethod *method = NULL; if (type == functionType) { method = Function_extractMethod(currentVal, argCount); } else if (type == keywordType) { - if (argCount == 1) method = &Global_Keyword_Method_1; - else if (argCount == 2) method = &Global_Keyword_Method_2; + if (argCount == 1) + method = &Global_Keyword_Method_1; + else if (argCount == 2) + method = &Global_Keyword_Method_2; } else if (type == persistentArrayMapType) { - if (argCount == 1) method = &Global_Map_Method_1; + if (argCount == 1) + method = &Global_Map_Method_1; } else if (type == persistentVectorType) { - if (argCount == 1) method = &Global_Vector_Method_1; + if (argCount == 1) + method = &Global_Vector_Method_1; } if (!method) { - throwIllegalStateException_C("Attempted to invoke non-callable value or invalid arity"); + throwIllegalStateException_C( + "Attempted to invoke non-callable value or invalid arity"); } // IC Ownership: The IC slot owns a reference to the callable object (key). diff --git a/backend-v2/runtime/Object.c b/backend-v2/runtime/Object.c index 6f266c90..dda765ad 100644 --- a/backend-v2/runtime/Object.c +++ b/backend-v2/runtime/Object.c @@ -79,6 +79,7 @@ extern uword_t Ptr_hash(void *self); extern bool Ptr_equals(void *self, void *other); extern bool Ptr_isReusable(void *self); bool equals_managed(RTValue self, RTValue other); +bool identical_managed(RTValue v1, RTValue v2); extern void Object_promoteToShared(Object *restrict self); extern void Object_promoteToSharedShallow(Object *restrict self, uword_t current); diff --git a/backend-v2/runtime/Object.h b/backend-v2/runtime/Object.h index f7bdc613..e9b11b3f 100644 --- a/backend-v2/runtime/Object.h +++ b/backend-v2/runtime/Object.h @@ -123,6 +123,7 @@ inline bool Ptr_isReusable(void *ptr); #include "PersistentVectorNode.h" #include "Ratio.h" #include "String.h" +#include "StringBuilder.h" #include "Symbol.h" #include "Var.h" @@ -302,6 +303,9 @@ inline void Object_destroy(Object *restrict self, bool deallocateChildren) { case bridgedObjectType: BridgedObject_destroy((BridgedObject *)self); break; + case stringBuilderType: + StringBuilder_destroy((StringBuilder *)self); + break; default: break; @@ -483,6 +487,8 @@ inline uword_t Object_hash(Object *restrict self) { return Exception_hash((Exception *)self); case bridgedObjectType: return BridgedObject_hash((BridgedObject *)self); + case stringBuilderType: + return StringBuilder_hash((StringBuilder *)self); default: assert(false && "Internal error: hash computation for NaN tagged types " "should be computed earlier."); @@ -566,6 +572,8 @@ inline bool Object_equals(Object *self, Object *other) { return Exception_equals((Exception *)self, (Exception *)other); case bridgedObjectType: return BridgedObject_equals((BridgedObject *)self, (BridgedObject *)other); + case stringBuilderType: + return StringBuilder_equals((StringBuilder *)self, (StringBuilder *)other); default: assert(false && "Internal error: hash computation for NaN tagged types " "should be computed earlier."); @@ -627,6 +635,8 @@ inline String *Object_toString(Object *restrict self) { return Exception_toString((Exception *)self); case bridgedObjectType: return BridgedObject_toString((BridgedObject *)self); + case stringBuilderType: + return StringBuilder_toString((StringBuilder *)self); default: assert(false && "Internal error: Object_toString got an unsupported type"); } @@ -721,6 +731,14 @@ inline bool equals_managed(RTValue self, RTValue other) { return result; } +inline bool identical_managed(RTValue v1, RTValue v2) { + bool result = (v1 == v2); + release(v1); + release(v2); + return result; +} + + #ifdef __cplusplus } #endif diff --git a/backend-v2/runtime/ObjectProto.h b/backend-v2/runtime/ObjectProto.h index 8ab11d36..01cafb2c 100644 --- a/backend-v2/runtime/ObjectProto.h +++ b/backend-v2/runtime/ObjectProto.h @@ -49,6 +49,7 @@ enum objectType { objectRootType = 20, exceptionType = 21, bridgedObjectType = 22, + stringBuilderType = 23, }; typedef enum objectType objectType; diff --git a/backend-v2/runtime/RuntimeInterface.c b/backend-v2/runtime/RuntimeInterface.c index aa42ee35..5a322bfb 100644 --- a/backend-v2/runtime/RuntimeInterface.c +++ b/backend-v2/runtime/RuntimeInterface.c @@ -2,6 +2,7 @@ #include "ConcurrentHashMap.h" #include "Ebr.h" +#include "ObjectProto.h" #include "PersistentArrayMap.h" #include "PersistentVector.h" #include "Symbol.h" @@ -77,8 +78,9 @@ void RuntimeInterface_cleanup() { void printReferenceCounts() { printf("Ref counters: "); // Iterate through all defined types in the enum - for (unsigned char i = stringType; i <= bridgedObjectType; i++) { - printf("%lu/%lu ", (unsigned long)allocationCount[i - 1], (unsigned long)objectCount[i - 1]); + for (unsigned char i = stringType; i <= stringBuilderType; i++) { + printf("%lu/%lu ", (unsigned long)allocationCount[i - 1], + (unsigned long)objectCount[i - 1]); } printf("\n"); } diff --git a/backend-v2/runtime/StringBuilder.c b/backend-v2/runtime/StringBuilder.c new file mode 100644 index 00000000..aeb609ef --- /dev/null +++ b/backend-v2/runtime/StringBuilder.c @@ -0,0 +1,38 @@ +#include "Object.h" +#include "String.h" +#include "StringBuilder.h" +#include +#include + +StringBuilder *StringBuilder_create(String *str) { + StringBuilder *self = (StringBuilder *)allocate(sizeof(StringBuilder)); + self->value = str; + Object_create((Object *)self, stringBuilderType); + return self; +} + +StringBuilder *StringBuilder_append(StringBuilder *self, + String *str_to_append) { + self->value = String_concat(self->value, str_to_append); + return self; +} + +String *StringBuilder_toString(StringBuilder *self) { + self->value = String_compactify(self->value); + Ptr_retain(self->value); + String *res = self->value; + Ptr_release(self); + return res; +} + +bool StringBuilder_equals(StringBuilder *self, StringBuilder *other) { + return String_equals(self->value, other->value); +} + +uword_t StringBuilder_hash(StringBuilder *self) { + return String_hash(self->value); +} + +void StringBuilder_destroy(StringBuilder *self) { + Ptr_release(self->value); +} diff --git a/backend-v2/runtime/StringBuilder.h b/backend-v2/runtime/StringBuilder.h new file mode 100644 index 00000000..dc3f072d --- /dev/null +++ b/backend-v2/runtime/StringBuilder.h @@ -0,0 +1,34 @@ +#ifndef RT_STRING_BUILDER +#define RT_STRING_BUILDER + +#ifdef __cplusplus +extern "C" { +#endif + +#include "defines.h" +#include "word.h" +#include + +typedef struct Object Object; +typedef struct String String; + +struct StringBuilder { + Object super; + String *value; +}; + +typedef struct StringBuilder StringBuilder; + +StringBuilder *StringBuilder_create(String *str); +StringBuilder *StringBuilder_append(StringBuilder *self, String *str_to_append); +String *StringBuilder_toString(StringBuilder *self); + +bool StringBuilder_equals(StringBuilder *self, StringBuilder *other); +uword_t StringBuilder_hash(StringBuilder *self); +void StringBuilder_destroy(StringBuilder *self); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/backend-v2/runtime/tests/StringBuilder_test.c b/backend-v2/runtime/tests/StringBuilder_test.c new file mode 100644 index 00000000..c159e8e5 --- /dev/null +++ b/backend-v2/runtime/tests/StringBuilder_test.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include + +#include "../Object.h" +#include "../String.h" +#include "../StringBuilder.h" +#include "TestTools.h" + +static void testStringBuilderBasic(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + String *s1 = String_createStatic("Hello"); + StringBuilder *sb = StringBuilder_create(s1); + + assert_int_equal(getType(RT_boxPtr(sb)), stringBuilderType); + + // toString should return "Hello" and release sb + String *res = StringBuilder_toString(sb); + assert_true(String_equals(res, s1)); + + Ptr_release(res); + }); +} + +static void testStringBuilderAppend(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + String *s1 = String_createStatic("Hello"); + String *s2 = String_createStatic(" World"); + StringBuilder *sb = StringBuilder_create(s1); + + // append World + sb = StringBuilder_append(sb, s2); + + String *res = StringBuilder_toString(sb); + String *expected = String_createStatic("Hello World"); + + assert_true(String_equals(res, expected)); + + Ptr_release(res); + Ptr_release(expected); + }); +} + +static void testStringBuilderMultipleAppend(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + StringBuilder *sb = StringBuilder_create(String_createStatic("A")); + sb = StringBuilder_append(sb, String_createStatic("B")); + sb = StringBuilder_append(sb, String_createStatic("C")); + sb = StringBuilder_append(sb, String_createStatic("D")); + + String *res = StringBuilder_toString(sb); + String *expected = String_createStatic("ABCD"); + + assert_true(String_equals(res, expected)); + + Ptr_release(res); + Ptr_release(expected); + }); +} + +static void testStringBuilderEqualsAndHash(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + String *s1 = String_createStatic("test"); + String *s2 = String_createStatic("test"); + StringBuilder *sb1 = StringBuilder_create(s1); + StringBuilder *sb2 = StringBuilder_create(s2); + + assert_true(StringBuilder_equals(sb1, sb2)); + assert_int_equal(StringBuilder_hash(sb1), StringBuilder_hash(sb2)); + + // Also test through Object methods + assert_true(Object_equals((Object*)sb1, (Object*)sb2)); + assert_int_equal(Object_hash((Object*)sb1), Object_hash((Object*)sb2)); + + Ptr_release(sb1); + Ptr_release(sb2); + }); +} + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(testStringBuilderBasic), + cmocka_unit_test(testStringBuilderAppend), + cmocka_unit_test(testStringBuilderMultipleAppend), + cmocka_unit_test(testStringBuilderEqualsAndHash), + }; + initialise_memory(); + RuntimeInterface_initialise(); + srand(0); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/backend-v2/state/ThreadsafeCompilerState.cpp b/backend-v2/state/ThreadsafeCompilerState.cpp index b7287045..db68072b 100644 --- a/backend-v2/state/ThreadsafeCompilerState.cpp +++ b/backend-v2/state/ThreadsafeCompilerState.cpp @@ -37,7 +37,6 @@ void ThreadsafeCompilerState::storeInternalClasses(RTValue from) { auto it = localMap.find(desc->parentName); if (it != localMap.end()) { parentClass = it->second; - Ptr_retain(parentClass); } else { // Look in global registry parentClass = classRegistry.getCurrent(desc->parentName.c_str()); @@ -45,14 +44,22 @@ void ThreadsafeCompilerState::storeInternalClasses(RTValue from) { } if (parentClass) { - Ptr_retain(parentClass); // desc release parentClass as well. + // 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); + } } else { for (auto const &p2 : localMap) Ptr_release(p2.second); @@ -72,14 +79,21 @@ void ThreadsafeCompilerState::storeInternalClasses(RTValue from) { Ptr_retain(c); classRegistry.registerObject(desc->name.c_str(), c); + if (desc->type.isDetermined()) { + c->registerId = (int32_t)desc->type.determinedType(); + } + + Ptr_retain(c); if (desc->type.isDetermined()) { // If we register by ID too, we need another reference because the ID // registry also releases on its own. - c->registerId = (int32_t)desc->type.determinedType(); classRegistry.registerObject(c, c->registerId); } else { - c->registerId = classRegistry.registerObject(c, -1); + c->registerId = (int32_t)classRegistry.registerObject(c, -1); } + + // Release the initial reference from Phase 1 + Ptr_release(c); } } diff --git a/backend-v2/tests/CMakeLists.txt b/backend-v2/tests/CMakeLists.txt index 667df25f..e1e65840 100644 --- a/backend-v2/tests/CMakeLists.txt +++ b/backend-v2/tests/CMakeLists.txt @@ -36,6 +36,7 @@ set(TEST_MODULES codegen/VarCallLeak_repro codegen/DynamicInvoke_test codegen/IFnInvoke_test + codegen/NewNodeDispatch_test codegen/EBRFlush_test state/ClassRegistration_test VarUAF_repro_test diff --git a/backend-v2/tests/codegen/NewNodeDispatch_test.cpp b/backend-v2/tests/codegen/NewNodeDispatch_test.cpp new file mode 100644 index 00000000..1414ce89 --- /dev/null +++ b/backend-v2/tests/codegen/NewNodeDispatch_test.cpp @@ -0,0 +1,100 @@ +#include "../../codegen/CodeGen.h" +#include "../../runtime/Function.h" +#include "../../runtime/Keyword.h" +#include "../../runtime/String.h" +#include "../../runtime/Var.h" +#include "../../runtime/StringBuilder.h" +#include "jit/JITEngine.h" +#include "../../tools/EdnParser.h" +#include "runtime/Ebr.h" +#include "runtime/RTValue.h" + +extern "C" { +#include "../../runtime/tests/TestTools.h" +#include +} + +using namespace rt; +using namespace clojure::rt::protobuf::bytecode; + +static void test_new_node_dynamic_dispatch(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::JITEngine engine; + auto &compState = engine.threadsafeState; + + // 1. Manually register StringBuilder class metadata + ::Class *sbClass = Class_create( + String_create("java.lang.StringBuilder"), + String_create("java.lang.StringBuilder"), + 0, NULL); + sbClass->registerId = stringBuilderType; + + ClassDescription *ext = new ClassDescription(); + IntrinsicDescription ctor; + ctor.type = CallType::Call; + ctor.symbol = "StringBuilder_create"; + ctor.argTypes = { ObjectTypeSet(stringType, false) }; + ctor.returnType = ObjectTypeSet(stringBuilderType, false); + ext->constructors.push_back(ctor); + + sbClass->compilerExtension = ext; + sbClass->compilerExtensionDestructor = delete_class_description; + compState.classRegistry.registerObject("java.lang.StringBuilder", sbClass); + + // 2. Create (let [x "Hello"] (new java.lang.StringBuilder x)) + // x in let is often inferred as ANY if we don't know better, + // but here it might be inferred as String. + // To force dynamic dispatch, we'll use a local that is intentionally opaque. + + Node letNode; + letNode.set_op(opLet); + auto *ln = letNode.mutable_subnode()->mutable_let(); + auto *b = ln->add_bindings(); + b->set_op(opBinding); + auto *bn = b->mutable_subnode()->mutable_binding(); + bn->set_name("x"); + bn->set_local(localTypeLet); + + auto *init = bn->mutable_init(); + init->set_op(opConst); + init->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); + init->mutable_subnode()->mutable_const_()->set_val("Hello"); + + auto *body = ln->mutable_body(); + body->set_op(opNew); + auto *nn = body->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("java.lang.StringBuilder"); + + auto *arg = nn->add_args(); + arg->set_op(opLocal); + arg->mutable_subnode()->mutable_local()->set_name("x"); + arg->mutable_subnode()->mutable_local()->set_local(localTypeLet); + + // 3. Compile and Run + auto res = engine.compileAST(letNode, "new_test").get(); + RTValue (*runFn)() = res.address.toPtr(); + RTValue result = runFn(); + + assert_int_equal(getType(result), stringBuilderType); + + // Convert to string to verify content + StringBuilder *sb = (StringBuilder *)RT_unboxPtr(result); + String *s = StringBuilder_toString(sb); + assert_string_equal(String_c_str(s), "Hello"); + Ptr_release(s); + + // 4. Cleanup + engine.retireModule("new_test"); + }); +} + +int main(void) { + initialise_memory(); + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_new_node_dynamic_dispatch), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/backend-v2/tests/codegen/Overflow_test.cpp b/backend-v2/tests/codegen/Overflow_test.cpp index fbe6806c..962f3195 100644 --- a/backend-v2/tests/codegen/Overflow_test.cpp +++ b/backend-v2/tests/codegen/Overflow_test.cpp @@ -37,10 +37,7 @@ static void setup_compiler_state(rt::ThreadsafeCompilerState &compState, // Initialize compiler state with metadata llvm::orc::ExecutorAddr resClasses = - engine - .compileAST(astClasses.nodes(0), "__classes") - .get() - .address; + engine.compileAST(astClasses.nodes(0), "__classes").get().address; RTValue classes = resClasses.toPtr()(); auto classesList = rt::buildClasses(classes); for (auto &desc : classesList) { @@ -83,10 +80,8 @@ static void test_integer_overflow_add(void **state) { ConstNode_ConstType_constTypeNumber); arg2->mutable_subnode()->mutable_const_()->set_val("1"); - auto resCall = engine - .compileAST(callNode, "__test_overflow_add") - .get() - .address; + auto resCall = + engine.compileAST(callNode, "__test_overflow_add").get().address; try { resPtrToValue(resCall); @@ -127,10 +122,50 @@ static void test_integer_overflow_sub(void **state) { ConstNode_ConstType_constTypeNumber); arg2->mutable_subnode()->mutable_const_()->set_val("1"); - auto resCall = engine - .compileAST(callNode, "__test_overflow_sub") - .get() - .address; + auto resCall = + engine.compileAST(callNode, "__test_overflow_sub").get().address; + + try { + resPtrToValue(resCall); + fail_msg("Should have thrown ArithmeticException"); + } catch (const LanguageException &e) { + std::string err = getExceptionString(e); + assert_true(err.find("Integer overflow") != std::string::npos); + } + }); +} + +static void test_integer_overflow_mul(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::JITEngine engine; + rt::ThreadsafeCompilerState &compState = engine.threadsafeState; + setup_compiler_state(compState, engine); + + // Create a StaticCallNode for (clojure.lang.Numbers/multiply 2000000000 2) + Node callNode; + callNode.set_op(opStaticCall); + callNode.set_tag("long"); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("multiply"); + + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->set_tag("long"); + arg1->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + arg1->mutable_subnode()->mutable_const_()->set_val("2000000000"); + + auto *arg2 = sc->add_args(); + arg2->set_op(opConst); + arg2->set_tag("long"); + arg2->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + arg2->mutable_subnode()->mutable_const_()->set_val("2"); + + auto resCall = + engine.compileAST(callNode, "__test_overflow_mul").get().address; try { resPtrToValue(resCall); @@ -147,6 +182,7 @@ int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_integer_overflow_add), cmocka_unit_test(test_integer_overflow_sub), + cmocka_unit_test(test_integer_overflow_mul), }; int result = cmocka_run_group_tests(tests, NULL, NULL); diff --git a/backend-v2/tests/rt-classes.edn b/backend-v2/tests/rt-classes.edn index 1ee8d376..1383cb38 100644 --- a/backend-v2/tests/rt-classes.edn +++ b/backend-v2/tests/rt-classes.edn @@ -300,7 +300,8 @@ {: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}]}} + {:args [:any :any] :type :call :symbol "equals_managed" :returns :bool} + {:args [:any :any] :type :call :symbol "identical_managed" :returns :bool}]}} diff --git a/backend-v2/tools/EdnParser.cpp b/backend-v2/tools/EdnParser.cpp index eda5ce02..b863b8f0 100644 --- a/backend-v2/tools/EdnParser.cpp +++ b/backend-v2/tools/EdnParser.cpp @@ -20,16 +20,27 @@ ClassDescription::~ClassDescription() { } } -TemporaryClassData::~TemporaryClassData() { +TemporaryClassData::TemporaryClassData(RTValue from) { + try { + scanMetadata(from); + } catch (...) { + clear(); + throw; + } +} + +void TemporaryClassData::clear() { for (auto const &d : classesByName) { Ptr_release(d.second); } + classesByName.clear(); for (auto const &d : classesById) { Ptr_release(d.second); } + classesById.clear(); } -TemporaryClassData::TemporaryClassData(RTValue from) { scanMetadata(from); } +TemporaryClassData::~TemporaryClassData() { clear(); } void TemporaryClassData::scanMetadata(RTValue from) { ConsumedValue root(from); @@ -89,21 +100,37 @@ void TemporaryClassData::scanMetadata(RTValue from) { if (getType(aliasWrapper.get()) == keywordType) { // toString consumes. String *ss = String_compactify(::toString(aliasWrapper.take())); + string sAlias = string(String_c_str(ss)); + auto it = classesByName.find(sAlias); + if (it != classesByName.end()) { + Ptr_release(it->second); + } Ptr_retain(pMap); // for storage - classesByName[string(String_c_str(ss))] = pMap; + classesByName[sAlias] = pMap; Ptr_release(ss); } else if (getType(aliasWrapper.get()) != nilType) { throwInternalInconsistencyException(":alias must be a keyword."); } + int32_t id = RT_unboxInt32(classIdWrapper.get()); + if (classesById.find(id) != classesById.end()) { + throwInternalInconsistencyException("Duplicate class id: " + + to_string(id)); + } + Ptr_retain(pMap); // for storage - classesById[RT_unboxInt32(classIdWrapper.get())] = pMap; + classesById[id] = pMap; // toString consumes. Protect borrowed key. retain(key); String *s = String_compactify(::toString(key)); + string sName = string(String_c_str(s)); + auto itName = classesByName.find(sName); + if (itName != classesByName.end()) { + Ptr_release(itName->second); + } Ptr_retain(pMap); // for storage - classesByName[string(String_c_str(s))] = pMap; + classesByName[sName] = pMap; Ptr_release(s); } } diff --git a/backend-v2/tools/EdnParser.h b/backend-v2/tools/EdnParser.h index 20f6ce95..bcac4659 100644 --- a/backend-v2/tools/EdnParser.h +++ b/backend-v2/tools/EdnParser.h @@ -21,6 +21,7 @@ class TemporaryClassData { uint32_t nextClassId = 1000; TemporaryClassData(RTValue from); ~TemporaryClassData(); + void clear(); }; enum class CallType { Call, Intrinsic, ClojureFn }; diff --git a/backend-v2/types/ObjectTypeSet.h b/backend-v2/types/ObjectTypeSet.h index 198b7337..1ebdf2dc 100644 --- a/backend-v2/types/ObjectTypeSet.h +++ b/backend-v2/types/ObjectTypeSet.h @@ -256,6 +256,7 @@ class ObjectTypeSet { retVal.insert(objectRootType); retVal.insert(exceptionType); retVal.insert(bridgedObjectType); + retVal.insert(stringBuilderType); retVal.allTypes = true; retVal.isBoxed = true; return retVal; @@ -316,6 +317,8 @@ class ObjectTypeSet { return "LE"; case bridgedObjectType: return "LB"; + case stringBuilderType: + return "LU"; default: return "LR"; } @@ -384,6 +387,8 @@ class ObjectTypeSet { return ":exception"; case bridgedObjectType: return ":bridged"; + case stringBuilderType: + return ":string-builder"; default: return ":custom(" + std::to_string(type) + ")"; } diff --git a/frontend/resources/rt-classes.edn b/frontend/resources/rt-classes.edn index 1ee8d376..4d0828a0 100644 --- a/frontend/resources/rt-classes.edn +++ b/frontend/resources/rt-classes.edn @@ -288,6 +288,13 @@ {:args [:this java.lang.String :int] :type :call :symbol "String_indexOfFrom" :returns :int}] replace [{:args [:this java.lang.String java.lang.String] :type :call :symbol "String_replace" :returns java.lang.String}]}} + java.lang.StringBuilder + {:extends java.lang.Object + :object-type 23 + :constructor [{:args [java.lang.String] :type :call :symbol "StringBuilder_create" :returns java.lang.StringBuilder}] + :instance-fns + {append [{:args [:this java.lang.String] :type :call :symbol "StringBuilder_append" :returns java.lang.StringBuilder}]}} + clojure.lang.Util {:static-fns {equiv [{:args [:int :int] :type :intrinsic :symbol "ICmpEQ" :returns :bool} @@ -300,8 +307,8 @@ {: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}]}} - + {:args [:any :any] :type :call :symbol "equals_managed" :returns :bool}] + identical [{:args [:any :any] :type :call :symbol "identical_managed" :returns :bool}]}} clojure.lang.PersistentVector diff --git a/tests/str.clj b/tests/str.clj new file mode 100644 index 00000000..980fec61 --- /dev/null +++ b/tests/str.clj @@ -0,0 +1,19 @@ +(defn str + "With no args, returns the empty string. With one arg x, returns + x.toString(). (str nil) returns the empty string. With more than + one arg, returns the concatenation of the str values of the args." + {:tag String + :added "1.0" + :static true} + (^String [] "") + (^String [^Object x] + (if (nil? x) "" (. x (toString)))) + (^String [x & ys] + ((fn [^StringBuilder sb more] + (if more + (recur (. sb (append (str (first more)))) (next more)) + (str sb))) + (new StringBuilder (str x)) ys))) + + +(str "aaa" "bbbb") \ No newline at end of file