diff --git a/SOUL.md b/SOUL.md new file mode 100644 index 0000000..86d582a --- /dev/null +++ b/SOUL.md @@ -0,0 +1,10 @@ +We're building a clojure JIT compiler in LLVM/C++ API. We use reference counting with consuming semantics and NaN boxing. + +There's two components here - one is the frontend - a clojure program that creates AST, computes memory guidance (dropMemory for happy path and unwindMemory for landing pads) +and builds the binary proto. + +The second - the backend which reads proto and compiles/executes. + +Our JIT currently compiles the proto forms using LLVMs ORC2 and links them, on IR level with runtime written in C - which allows for huge inlining optimisations. + +When working with tests - always stop and ask for permission if you want to change non-test code. \ No newline at end of file diff --git a/backend-v2/bridge/InstanceCallStub.cpp b/backend-v2/bridge/InstanceCallStub.cpp index e21c6eb..ff5a76f 100644 --- a/backend-v2/bridge/InstanceCallStub.cpp +++ b/backend-v2/bridge/InstanceCallStub.cpp @@ -83,7 +83,7 @@ InstanceCallSlowPath(void *slot, const char *methodName, int32_t argCount, * 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 diff --git a/backend-v2/codegen/CodeGen.cpp b/backend-v2/codegen/CodeGen.cpp index 90a5436..1b57ed8 100644 --- a/backend-v2/codegen/CodeGen.cpp +++ b/backend-v2/codegen/CodeGen.cpp @@ -324,6 +324,9 @@ llvm::Function *CodeGen::generateBaselineMethod( valueEncoder.unboxDouble(TypedValue(type.boxed(), valRaw)).value; } else if (type.isUnboxedType(booleanType)) { val = valueEncoder.unboxBool(TypedValue(type.boxed(), valRaw)).value; + } else if (type.isUnboxedPointer()) { + val = + valueEncoder.unboxPointer(TypedValue(type.boxed(), valRaw)).value; } } diff --git a/backend-v2/codegen/LLVMTypes.cpp b/backend-v2/codegen/LLVMTypes.cpp index e46f2f3..b5b623d 100644 --- a/backend-v2/codegen/LLVMTypes.cpp +++ b/backend-v2/codegen/LLVMTypes.cpp @@ -18,15 +18,17 @@ LLVMTypes::LLVMTypes(LLVMContext &context) { voidTy = Type::getVoidTy(context); RT_valueTy = i64Ty; - RT_objectTy = StructType::create(context, - { - wordTy, // atomicRefCount - i32Ty, // type (enum) + std::vector objectFields = {wordTy, i32Ty}; + if (K_WORD_SIZE == 8) { #ifdef USE_MEMORY_BANKS - Type::getInt8Ty(Ctx) // bankId + objectFields.push_back(i8Ty); // bankId + objectFields.push_back(ArrayType::get(i8Ty, 3)); // padding to 16 +#else + objectFields.push_back(i32Ty); // padding to 16 #endif - }, - "Clojure_Object"); + } + + RT_objectTy = StructType::create(context, objectFields, "Clojure_Object"); frameTy = StructType::create(context, {ptrTy, // leafFrame diff --git a/backend-v2/codegen/invoke/InvokeManager_InstanceCall.cpp b/backend-v2/codegen/invoke/InvokeManager_InstanceCall.cpp index 9780cc9..1205ccd 100644 --- a/backend-v2/codegen/invoke/InvokeManager_InstanceCall.cpp +++ b/backend-v2/codegen/invoke/InvokeManager_InstanceCall.cpp @@ -1,10 +1,10 @@ #include "../../bridge/Exceptions.h" #include "../../tools/EdnParser.h" #include "../../tools/RTValueWrapper.h" +#include "../../tools/RandomID.h" #include "../../types/ConstantClass.h" #include "../CodeGen.h" #include "InvokeManager.h" -#include "../../tools/RandomID.h" #include "llvm/IR/MDBuilder.h" #include #include @@ -38,8 +38,8 @@ TypedValue InvokeManager::generateDynamicInstanceCall( "mode"); } - cout << "Generating dynamic instance call for method: " << methodName << endl; - // Allocate an Inline Cache slot as a global variable in the module + // cout << "Generating dynamic instance call for method: " << methodName << + // endl; Allocate an Inline Cache slot as a global variable in the module auto *slotTy = StructType::get(TheContext, {types.wordTy, types.ptrTy}); std::stringstream ss; @@ -379,8 +379,9 @@ TypedValue InvokeManager::generateDeterminedInstanceCall( uint32_t target = fid->argTypes[i].determinedType(); Value *targetVal = ConstantInt::get(this->types.i32Ty, (uint32_t)target); - Value *isType = - this->builder.CreateICmpEQ(this->builder.CreateTrunc(argRuntimeTypes[i], this->types.i32Ty), targetVal); + Value *isType = 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/ops/ConstNode.cpp b/backend-v2/codegen/ops/ConstNode.cpp index ece4f04..09c281f 100644 --- a/backend-v2/codegen/ops/ConstNode.cpp +++ b/backend-v2/codegen/ops/ConstNode.cpp @@ -141,7 +141,7 @@ ObjectTypeSet CodeGen::getType(const Node &node, const ConstNode &subnode, node.otag() == "clojure.lang.Ratio" || node.tag() == "class clojure.lang.Ratio" || node.otag() == "class clojure.lang.Ratio") { - return ObjectTypeSet(ratioType, true, new ConstantRatio(subnode.val())) + return ObjectTypeSet(ratioType, false, new ConstantRatio(subnode.val())) .restriction(typeRestrictions); } @@ -149,7 +149,7 @@ ObjectTypeSet CodeGen::getType(const Node &node, const ConstNode &subnode, node.otag() == "clojure.lang.BigInt" || node.tag() == "class clojure.lang.BigInt" || node.otag() == "class clojure.lang.BigInt") { - return ObjectTypeSet(bigIntegerType, true, + return ObjectTypeSet(bigIntegerType, false, new ConstantBigInteger(subnode.val())) .restriction(typeRestrictions); } @@ -173,7 +173,7 @@ ObjectTypeSet CodeGen::getType(const Node &node, const ConstNode &subnode, new ConstantInteger((int32_t)val)) .restriction(typeRestrictions); } else { - return ObjectTypeSet(bigIntegerType, true, + return ObjectTypeSet(bigIntegerType, false, new ConstantBigInteger(subnode.val())) .restriction(typeRestrictions); } diff --git a/backend-v2/codegen/ops/VarNode.cpp b/backend-v2/codegen/ops/VarNode.cpp index aee05aa..dfb18e3 100644 --- a/backend-v2/codegen/ops/VarNode.cpp +++ b/backend-v2/codegen/ops/VarNode.cpp @@ -11,7 +11,7 @@ TypedValue CodeGen::codegen(const Node &node, const VarNode &subnode, const ObjectTypeSet &typeRestrictions) { auto varName = subnode.var().substr(2); ScopedRef var(compilerState.varRegistry.getCurrent(varName.c_str())); - cout << "Derefing var " << varName << endl; + if (!var) throwCodeGenerationException( "Unable to resolve var: " + varName + " in this context", node); diff --git a/backend-v2/runtime/Ebr.c b/backend-v2/runtime/Ebr.c index 0191981..7127824 100644 --- a/backend-v2/runtime/Ebr.c +++ b/backend-v2/runtime/Ebr.c @@ -175,23 +175,42 @@ void Ebr_unregister_thread() { void Ebr_force_reclaim() { // There should be only two threads left: the current thread and the reclaimer - while (atomic_load(&active_threads) > 2) + int wait_ms = 0; + while (atomic_load(&active_threads) > 2) { usleep(1000); - + wait_ms++; + if (wait_ms >= 1000 && (wait_ms % 1000 == 0)) { + fprintf(stderr, + "[EBR WARNING] Ebr_force_reclaim waiting: active_threads = %zu " + "(> 2). Waited %d seconds.\n", + (size_t)atomic_load(&active_threads), wait_ms / 1000); + } + } + // Keep reclaiming until there are no more objects to reclaim. - // We use the synchronization_mutex to properly wait for the background + // We use the synchronization_mutex to properly wait for the background // reclaimer thread if it is currently holding the "stolen" pending list. + int sync_wait_ms = 0; while (true) { Ebr_flush_critical(); Ebr_synchronize_and_reclaim(); pthread_mutex_lock(&synchronization_mutex); - bool is_empty = (atomic_load_explicit(&pending_reclamation, memory_order_acquire) == NULL); + bool is_empty = (atomic_load_explicit(&pending_reclamation, + memory_order_acquire) == NULL); pthread_mutex_unlock(&synchronization_mutex); if (is_empty) { break; } + + usleep(1000); + sync_wait_ms++; + if (sync_wait_ms >= 1000 && (sync_wait_ms % 1000 == 0)) { + fprintf(stderr, + "[EBR WARNING] Ebr_force_reclaim waiting to empty pending_reclamation. Waited %d seconds.\n", + sync_wait_ms / 1000); + } } } diff --git a/backend-v2/runtime/Function.c b/backend-v2/runtime/Function.c index ad8adc6..d1bc64c 100644 --- a/backend-v2/runtime/Function.c +++ b/backend-v2/runtime/Function.c @@ -260,3 +260,14 @@ struct FunctionMethod *RT_updateICSlot(void *slot, RTValue currentVal, } return method; } + +void Function_promoteToShared(ClojureFunction *self, uword_t count) { + for (uword_t i = 0; i < self->methodCount; i++) { + FunctionMethod *method = &(self->methods[i]); + for (uword_t j = 0; j < method->closedOversCount; j++) { + promoteToShared(method->closedOvers[j]); + } + } + Object_promoteToSharedShallow((Object *)self, count); +} + diff --git a/backend-v2/runtime/Function.h b/backend-v2/runtime/Function.h index 6df5ddc..61d241b 100644 --- a/backend-v2/runtime/Function.h +++ b/backend-v2/runtime/Function.h @@ -62,6 +62,8 @@ uword_t Function_hash(ClojureFunction *self); String *Function_toString(ClojureFunction *self); void Function_destroy(ClojureFunction *self); void Function_cleanupOnce(ClojureFunction *self); +void Function_promoteToShared(ClojureFunction *self, uword_t count); + RTValue RT_invokeDynamic(RTValue funObj, RTValue *args, int32_t argCount); FunctionMethod *Function_extractMethod(RTValue funObj, uword_t argCount); struct FunctionMethod *RT_updateICSlot(void *slot, RTValue currentVal, diff --git a/backend-v2/runtime/Object.h b/backend-v2/runtime/Object.h index e9b11b3..b1522ee 100644 --- a/backend-v2/runtime/Object.h +++ b/backend-v2/runtime/Object.h @@ -447,6 +447,10 @@ inline void Object_promoteToShared(Object *restrict self) { PersistentVectorNode_promoteToShared((PersistentVectorNode *)self, count); break; + case functionType: + Function_promoteToShared((ClojureFunction *)self, count); + break; + default: Object_promoteToSharedShallow(self, count); break; diff --git a/backend-v2/state/ThreadsafeRegistry.h b/backend-v2/state/ThreadsafeRegistry.h index 959852c..74b17ff 100644 --- a/backend-v2/state/ThreadsafeRegistry.h +++ b/backend-v2/state/ThreadsafeRegistry.h @@ -4,6 +4,7 @@ #include "../RuntimeHeaders.h" #include "../bridge/Exceptions.h" #include "runtime/Object.h" +#include "runtime/RTValue.h" #include #include #include @@ -45,6 +46,7 @@ template class ThreadsafeRegistry { auto it = registry.find(key); if (manageRuntimeMemory && it != registry.end()) { + promoteToShared(RT_boxPtr(it->second)); Ptr_release((void *)it->second); } @@ -58,15 +60,20 @@ template class ThreadsafeRegistry { auto it = registry.find(key); if (it != registry.end()) { - if (manageRuntimeMemory) + if (manageRuntimeMemory) { Ptr_retain((void *)it->second); + } return it->second; } T *newDef = factory(); registry[key] = newDef; - if (manageRuntimeMemory) - Ptr_retain(newDef); // Still need to return a fresh reference if it's being returned + if (manageRuntimeMemory) { + promoteToShared(RT_boxPtr(newDef)); + Ptr_retain(newDef); // Still need to return a fresh reference if it's + // being returned + } + return newDef; } diff --git a/backend-v2/tests/CMakeLists.txt b/backend-v2/tests/CMakeLists.txt index e1e6584..b7b066d 100644 --- a/backend-v2/tests/CMakeLists.txt +++ b/backend-v2/tests/CMakeLists.txt @@ -32,11 +32,15 @@ set(TEST_MODULES codegen/ThrowNode_test codegen/FnNode_test codegen/InvokeNode_test + codegen/InvokeMemory_test codegen/VarCallIC_test codegen/VarCallLeak_repro codegen/DynamicInvoke_test codegen/IFnInvoke_test codegen/NewNodeDispatch_test + codegen/VariadicInvoke_test + codegen/Concurrency_test + codegen/RecurNode_test codegen/EBRFlush_test state/ClassRegistration_test VarUAF_repro_test diff --git a/backend-v2/tests/codegen/Concurrency_test.cpp b/backend-v2/tests/codegen/Concurrency_test.cpp new file mode 100644 index 0000000..d638739 --- /dev/null +++ b/backend-v2/tests/codegen/Concurrency_test.cpp @@ -0,0 +1,183 @@ +#include "../../codegen/CodeGen.h" +#include "../../jit/JITEngine.h" +#include "../../runtime/Numbers.h" +#include "../../runtime/Object.h" +#include "../../runtime/String.h" +#include "../../runtime/Var.h" +#include "../../tools/EdnParser.h" +#include +#include +#include +#include +#include + +extern "C" { +#include "../../runtime/tests/TestTools.h" +#include + +void delete_class_description(void *ptr); +} + +using namespace rt; +using namespace clojure::rt::protobuf::bytecode; + +static void setup_minimal_runtime(JITEngine &engine) { + auto &compState = engine.threadsafeState; + String *nameStr = String_create("clojure.lang.Numbers"); + Ptr_retain(nameStr); + ::Class *cls = Class_create(nameStr, nameStr, 0, nullptr); + ClassDescription *ext = new ClassDescription(); + ext->name = "clojure.lang.Numbers"; + IntrinsicDescription add; + add.symbol = "Numbers_add"; + add.type = rt::CallType::Call; + add.argTypes = {ObjectTypeSet::all(), ObjectTypeSet::all()}; + add.returnType = ObjectTypeSet::all(); + ext->staticFns["add"].push_back(add); + cls->compilerExtension = ext; + cls->compilerExtensionDestructor = ::delete_class_description; + compState.classRegistry.registerObject("clojure.lang.Numbers", cls); +} + +static void test_concurrent_closures(void **state) { + (void)state; + // We increase the chance of catching race conditions by running many + // iterations. However, for standard unit tests, we keep it reasonable. + + ASSERT_MEMORY_ALL_BALANCED({ + JITEngine engine; + setup_minimal_runtime(engine); + + // 1. Create the closure with a capture + // (let [x 100N] (fn [y] (+ x y))) + Node fnDef; + fnDef.set_op(opLet); + auto *let = fnDef.mutable_subnode()->mutable_let(); + auto *bx = let->add_bindings(); + bx->set_op(opBinding); + bx->mutable_subnode()->mutable_binding()->set_name("x"); + bx->mutable_subnode()->mutable_binding()->mutable_init()->set_op(opConst); + bx->mutable_subnode() + ->mutable_binding() + ->mutable_init() + ->mutable_subnode() + ->mutable_const_() + ->set_val("100"); + bx->mutable_subnode() + ->mutable_binding() + ->mutable_init() + ->mutable_subnode() + ->mutable_const_() + ->set_type(ConstNode_ConstType_constTypeNumber); + bx->mutable_subnode()->mutable_binding()->mutable_init()->set_tag( + "clojure.lang.BigInt"); + + auto *fn = let->mutable_body(); + fn->set_op(opFn); + auto *f = fn->mutable_subnode()->mutable_fn(); + auto *mn = f->add_methods()->mutable_subnode()->mutable_fnmethod(); + mn->set_fixedarity(1); + mn->add_params()->mutable_subnode()->mutable_binding()->set_name("y"); + mn->add_closedovers()->set_op(opLocal); + mn->mutable_closedovers(0)->mutable_subnode()->mutable_local()->set_name( + "x"); + mn->mutable_closedovers(0)->mutable_subnode()->mutable_local()->set_local( + localTypeLet); + + // Guidance to align with frontend IR EXACTLY: + // 1. On fn node: release the let-binding reference on exception path (Match EDN: :unwind-memory [["x__#0" -1]]) + auto *gUnwind = fn->add_unwindmemory(); + gUnwind->set_variablename("x"); + gUnwind->set_requiredrefcountchange(-1); + + auto *plus = mn->mutable_body(); + plus->set_op(opStaticCall); + auto *sc = plus->mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("add"); + sc->add_args()->set_op(opLocal); + sc->mutable_args(0)->mutable_subnode()->mutable_local()->set_name("x"); + sc->mutable_args(0)->mutable_subnode()->mutable_local()->set_local( + localTypeLet); + sc->add_args()->set_op(opLocal); + sc->mutable_args(1)->mutable_subnode()->mutable_local()->set_name("y"); + sc->mutable_args(1)->mutable_subnode()->mutable_local()->set_local( + localTypeArg); + + // Guidance to align with frontend IR: + // On static-call node: retain 'x' before call (since Numbers/add consumes its args) + auto *gPlus = plus->add_dropmemory(); + gPlus->set_variablename("x"); + gPlus->set_requiredrefcountchange(1); + + auto builderAddr = + engine.compileAST(fnDef, "closure_builder").get().address; + RTValue funObj = builderAddr.toPtr()(); + + // Promote the function object and its captures to shared! + promoteToShared(funObj); + + // Find/Create the Var. Var_create returns with +1 (shared). + const char *varName = "user/shared-fn"; + Var *v = Var_create(Keyword_create(String_create(varName))); + Ptr_retain(v); // For Var_bindRoot (consumes 1) + Var_bindRoot(v, funObj); + engine.threadsafeState.varRegistry.registerObject(varName, v); + + // 2. Create the caller + // (fn [] (user/shared-fn 5)) + Node callerDef; + callerDef.set_op(opInvoke); + auto *inv = callerDef.mutable_subnode()->mutable_invoke(); + inv->mutable_fn()->set_op(opVar); + inv->mutable_fn()->mutable_subnode()->mutable_var()->set_var( + std::string("#'") + varName); + auto *arg = inv->add_args(); + arg->set_op(opConst); + arg->mutable_subnode()->mutable_const_()->set_val("5"); + arg->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + + auto callerAddr = engine.compileAST(callerDef, "caller").get().address; + typedef RTValue (*CallerFn)(); + CallerFn caller = (CallerFn)callerAddr.getValue(); + + // 3. Spawning threads + std::atomic start{false}; + const int numThreads = 8; + const int iterations = 1000; + std::vector threads; + + for (int i = 0; i < numThreads; ++i) { + threads.emplace_back([&]() { + while (!start.load()) + std::this_thread::yield(); + for (int j = 0; j < iterations; ++j) { + RTValue res = caller(); + if (getType(res) != bigIntegerType || + mpz_get_si(((BigInteger *)RT_unboxPtr(res))->value) != 105) { + fprintf(stderr, "Incorrect result in thread! Type: %d, Val: %ld\n", + getType(res), + RT_isPtr(res) ? (long)mpz_get_si( + ((BigInteger *)RT_unboxPtr(res))->value) + : (long)RT_unboxInt32(res)); + abort(); + } + release(res); + } + }); + } + + start.store(true); + for (auto &t : threads) + t.join(); + }); +} + +int main(void) { + initialise_memory(); + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_concurrent_closures), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/backend-v2/tests/codegen/FnNode_test.cpp b/backend-v2/tests/codegen/FnNode_test.cpp index 754823c..0a3add4 100644 --- a/backend-v2/tests/codegen/FnNode_test.cpp +++ b/backend-v2/tests/codegen/FnNode_test.cpp @@ -216,12 +216,65 @@ static void test_multi_arity_fn(void **state) { }); } +static void test_fn_bigint_arg(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::JITEngine engine; + + // AST: (let [f (fn [x] x)] (f 123N)) + Node letNode; + letNode.set_op(opLet); + auto *let = letNode.mutable_subnode()->mutable_let(); + + auto *b = let->add_bindings(); + b->set_op(opBinding); + auto *bn = b->mutable_subnode()->mutable_binding(); + bn->set_name("f"); + auto *init = bn->mutable_init(); + init->set_op(opFn); + auto *fn = init->mutable_subnode()->mutable_fn(); + fn->set_maxfixedarity(1); + auto *m = fn->add_methods(); + auto *mn = m->mutable_subnode()->mutable_fnmethod(); + mn->set_fixedarity(1); + auto *p = mn->add_params(); + p->set_op(opBinding); + p->mutable_subnode()->mutable_binding()->set_name("x"); + auto *body = mn->mutable_body(); + body->set_op(opLocal); + body->mutable_subnode()->mutable_local()->set_name("x"); + + auto *letBody = let->mutable_body(); + letBody->set_op(opInvoke); + auto *inv = letBody->mutable_subnode()->mutable_invoke(); + inv->mutable_fn()->set_op(opLocal); + inv->mutable_fn()->mutable_subnode()->mutable_local()->set_name("f"); + inv->mutable_fn()->mutable_subnode()->mutable_local()->set_local(localTypeLet); + + auto *arg = inv->add_args(); + arg->set_op(opConst); + arg->mutable_subnode()->mutable_const_()->set_val("123"); + arg->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg->set_tag("clojure.lang.BigInt"); + + auto res = engine.compileAST(letNode, "fn_bigint_arg_test").get(); + RTValue (*func)() = res.address.toPtr(); + RTValue result = func(); + + assert_int_equal(bigIntegerType, getType(result)); + assert_int_equal(123, mpz_get_si(((BigInteger*)RT_unboxPtr(result))->value)); + + release(result); + }); +} + int main(void) { initialise_memory(); const struct CMUnitTest tests[] = { cmocka_unit_test(test_simple_fn), cmocka_unit_test(test_fn_capture_unboxing_int), cmocka_unit_test(test_multi_arity_fn), + cmocka_unit_test(test_fn_bigint_arg), }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/backend-v2/tests/codegen/InstanceCallIC_test.cpp b/backend-v2/tests/codegen/InstanceCallIC_test.cpp index 54ae439..a8c6b46 100644 --- a/backend-v2/tests/codegen/InstanceCallIC_test.cpp +++ b/backend-v2/tests/codegen/InstanceCallIC_test.cpp @@ -219,9 +219,9 @@ static void test_instance_call_ic_atomicity(void **state) { release(fn()); } - const int numThreads = 8; - const int iterationsForSwitcher = 1000; - const int iterationsForObservers = 100000; + const int numThreads = 16; + const int iterationsForSwitcher = 5000; + const int iterationsForObservers = 500000; std::atomic failed{false}; std::atomic readyCount{0}; std::atomic start{false}; diff --git a/backend-v2/tests/codegen/InvokeMemory_test.cpp b/backend-v2/tests/codegen/InvokeMemory_test.cpp new file mode 100644 index 0000000..835e4cb --- /dev/null +++ b/backend-v2/tests/codegen/InvokeMemory_test.cpp @@ -0,0 +1,286 @@ +#include "../../codegen/CodeGen.h" +#include "../../jit/JITEngine.h" +#include "../../runtime/Ebr.h" +#include "../../runtime/Numbers.h" +#include "../../runtime/String.h" +#include "../../tools/EdnParser.h" +#include +#include + +#include "../../runtime/Exception.h" +#include "../../runtime/Object.h" + +extern "C" { +#include "../../runtime/tests/TestTools.h" +#include + +void delete_class_description(void *ptr); +void *createException_C(const char *className, String *message, + RTValue payload); +String *String_create(const char *s); +RTValue Exception_create(); +} + +extern "C" RTValue Exception_create() { + String *msg = String_create(""); + ::Exception *self = (::Exception *)allocate(sizeof(::Exception)); + Object_create((Object *)self, exceptionType); + self->bridgedData = createException_C("java.lang.Exception", msg, 0); + return RT_boxPtr(self); +} + +using namespace rt; +using namespace clojure::rt::protobuf::bytecode; + +// Minimal runtime setup to avoid leaks in the full EDN-based loader +static void setup_minimal_runtime(JITEngine &engine) { + auto &compState = engine.threadsafeState; + + { + String *nameStr = String_create("clojure.lang.Numbers"); + Ptr_retain(nameStr); + ::Class *cls = Class_create(nameStr, nameStr, 0, nullptr); + ClassDescription *ext = new ClassDescription(); + ext->name = "clojure.lang.Numbers"; + IntrinsicDescription add; + add.symbol = "Numbers_add"; + add.type = rt::CallType::Call; + add.argTypes = {ObjectTypeSet::all(), ObjectTypeSet::all()}; + add.returnType = ObjectTypeSet::all(); + ext->staticFns["add"].push_back(add); + cls->compilerExtension = ext; + cls->compilerExtensionDestructor = ::delete_class_description; + compState.classRegistry.registerObject("clojure.lang.Numbers", cls); + } + + { + String *nameStr = String_create("java.lang.Exception"); + Ptr_retain(nameStr); + ::Class *cls = Class_create(nameStr, nameStr, 0, nullptr); + cls->registerId = exceptionType; + ClassDescription *ext = new ClassDescription(); + ext->name = "java.lang.Exception"; + + // Add default constructor + IntrinsicDescription ctor; + ctor.symbol = "Exception_create"; + ctor.type = rt::CallType::Call; + ctor.argTypes = {}; + ctor.returnType = ObjectTypeSet(exceptionType).boxed(); + ext->constructors.push_back(ctor); + + cls->compilerExtension = ext; + cls->compilerExtensionDestructor = ::delete_class_description; + Ptr_retain(cls); + compState.classRegistry.registerObject(cls, cls->registerId); + compState.classRegistry.registerObject("java.lang.Exception", cls); + } +} + +// Test Case 1: Functions with Captures +static void test_invoke_captures_memory(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + JITEngine engine(llvm::OptimizationLevel::O0, false); + setup_minimal_runtime(engine); + + // (let [x 10N] (let [f (fn [y] (+ x y))] (f 5))) + Node fnNode; + fnNode.set_op(opFn); + auto *fn = fnNode.mutable_subnode()->mutable_fn(); + auto *m = fn->add_methods(); + auto *mn = m->mutable_subnode()->mutable_fnmethod(); + mn->set_fixedarity(1); + mn->add_params()->mutable_subnode()->mutable_binding()->set_name("y"); + mn->add_closedovers()->set_op(opLocal); + mn->mutable_closedovers(0)->mutable_subnode()->mutable_local()->set_name( + "x"); + mn->mutable_closedovers(0)->mutable_subnode()->mutable_local()->set_local( + localTypeLet); + + auto *plus = mn->mutable_body(); + plus->set_op(opStaticCall); + auto *sc = plus->mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("add"); + sc->add_args()->set_op(opLocal); + sc->mutable_args(0)->mutable_subnode()->mutable_local()->set_name("x"); + sc->mutable_args(0)->mutable_subnode()->mutable_local()->set_local( + localTypeLet); + sc->add_args()->set_op(opLocal); + sc->mutable_args(1)->mutable_subnode()->mutable_local()->set_name("y"); + sc->mutable_args(1)->mutable_subnode()->mutable_local()->set_local( + localTypeArg); + + auto *gPlus = plus->add_dropmemory(); + gPlus->set_variablename("x"); + gPlus->set_requiredrefcountchange(1); + + auto *gUnwindFn = fnNode.add_unwindmemory(); + gUnwindFn->set_variablename("x"); + gUnwindFn->set_requiredrefcountchange(-1); + + Node root; + root.set_op(opLet); + auto *letX = root.mutable_subnode()->mutable_let(); + auto *bx = letX->add_bindings(); + bx->set_op(opBinding); + bx->mutable_subnode()->mutable_binding()->set_name("x"); + auto *ia1 = bx->mutable_subnode()->mutable_binding()->mutable_init(); + ia1->set_op(opConst); + ia1->mutable_subnode()->mutable_const_()->set_val("10"); + ia1->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + ia1->set_tag("clojure.lang.BigInt"); + + auto *letF_Node = letX->mutable_body(); + letF_Node->set_op(opLet); + auto *letF = letF_Node->mutable_subnode()->mutable_let(); + auto *bf = letF->add_bindings(); + bf->set_op(opBinding); + bf->mutable_subnode()->mutable_binding()->set_name("f"); + bf->mutable_subnode()->mutable_binding()->mutable_init()->CopyFrom(fnNode); + + auto *do_Node = letF->mutable_body(); + do_Node->set_op(opDo); + auto *do_ = do_Node->mutable_subnode()->mutable_do_(); + + auto *invNode = do_->mutable_ret(); + invNode->set_op(opInvoke); + auto *inv = invNode->mutable_subnode()->mutable_invoke(); + inv->mutable_fn()->set_op(opLocal); + inv->mutable_fn()->mutable_subnode()->mutable_local()->set_name("f"); + inv->mutable_fn()->mutable_subnode()->mutable_local()->set_local( + localTypeLet); + auto *arg = inv->add_args(); + arg->set_op(opConst); + arg->mutable_subnode()->mutable_const_()->set_val("5"); + arg->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + + // [NEW] Aligning with dump: invoke has unwind-memory for f + auto *gUnwindF = invNode->add_unwindmemory(); + gUnwindF->set_variablename("f"); + gUnwindF->set_requiredrefcountchange(-1); + + auto res = engine.compileAST(root, "test1").get(); + cout << res.optimizedIR << endl; + RTValue (*func)() = res.address.toPtr(); + RTValue result = func(); + + assert_int_equal(bigIntegerType, getType(result)); + assert_int_equal(15, + mpz_get_si(((BigInteger *)RT_unboxPtr(result))->value)); + release(result); + }); +} + +// Test Case 2: Exception Unwinding +static void test_invoke_exception_unwinding(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + JITEngine engine(llvm::OptimizationLevel::O0, false); + setup_minimal_runtime(engine); + // (let [f (fn [x y] (throw (new java.lang.Exception)))] + // (f 10 (f 20 30))) + Node throwerFn; + throwerFn.set_op(opFn); + auto *fn = throwerFn.mutable_subnode()->mutable_fn(); + auto *mn = fn->add_methods()->mutable_subnode()->mutable_fnmethod(); + mn->set_fixedarity(2); + mn->add_params()->mutable_subnode()->mutable_binding()->set_name("x__#0"); + mn->add_params()->mutable_subnode()->mutable_binding()->set_name("y__#0"); + auto *body = mn->mutable_body(); + body->set_op(opThrow); + auto *dmX = body->add_dropmemory(); + dmX->set_variablename("x__#0"); + dmX->set_requiredrefcountchange(-1); + auto *dmY = body->add_dropmemory(); + dmY->set_variablename("y__#0"); + dmY->set_requiredrefcountchange(-1); + auto *ex = body->mutable_subnode()->mutable_throw_()->mutable_exception(); + ex->set_op(opNew); + auto *nn = ex->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.Exception"); + + Node root; + root.set_op(opLet); + auto *let = root.mutable_subnode()->mutable_let(); + auto *bn = let->add_bindings()->mutable_subnode()->mutable_binding(); + bn->set_name("f__#0"); + bn->mutable_init()->CopyFrom(throwerFn); + + auto *inv = let->mutable_body(); + inv->set_op(opInvoke); + auto *i = inv->mutable_subnode()->mutable_invoke(); + i->mutable_fn()->set_op(opLocal); + i->mutable_fn()->mutable_subnode()->mutable_local()->set_name("f__#0"); + i->mutable_fn()->mutable_subnode()->mutable_local()->set_local( + localTypeLet); + + auto *uFn = i->mutable_fn()->add_unwindmemory(); + uFn->set_variablename("f__#0"); + uFn->set_requiredrefcountchange(-1); + auto *dFn = i->mutable_fn()->add_dropmemory(); + dFn->set_variablename("f__#0"); + dFn->set_requiredrefcountchange(1); + + auto *arg1 = i->add_args(); + arg1->set_op(opConst); + arg1->mutable_subnode()->mutable_const_()->set_val("10"); + arg1->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + arg1->set_tag("clojure.lang.BigInt"); + + auto *uArg1 = arg1->add_unwindmemory(); + uArg1->set_variablename("f__#0"); + uArg1->set_requiredrefcountchange(-1); + + auto *arg2 = i->add_args(); + arg2->set_op(opInvoke); + auto *i2 = arg2->mutable_subnode()->mutable_invoke(); + i2->mutable_fn()->set_op(opLocal); + i2->mutable_fn()->mutable_subnode()->mutable_local()->set_name("f__#0"); + i2->mutable_fn()->mutable_subnode()->mutable_local()->set_local( + localTypeLet); + auto *uFn2 = i2->mutable_fn()->add_unwindmemory(); + uFn2->set_variablename("f__#0"); + uFn2->set_requiredrefcountchange(-1); + auto *a2_1 = i2->add_args(); + a2_1->set_op(opConst); + a2_1->mutable_subnode()->mutable_const_()->set_val("20"); + a2_1->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + auto *a2_2 = i2->add_args(); + a2_2->set_op(opConst); + a2_2->mutable_subnode()->mutable_const_()->set_val("30"); + a2_2->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + + // User AST: arg2 has no unwind-memory on itself + + auto res = engine.compileAST(root, "test2").get(); + RTValue (*func)() = res.address.toPtr(); + + bool caught = false; + try { + func(); + } catch (const LanguageException &e) { + caught = true; + } + assert_true(caught); + }); +} + +int main(void) { + initialise_memory(); + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_invoke_captures_memory), + cmocka_unit_test(test_invoke_exception_unwinding), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/backend-v2/tests/codegen/LetNode_test.cpp b/backend-v2/tests/codegen/LetNode_test.cpp index c338b17..2144db1 100644 --- a/backend-v2/tests/codegen/LetNode_test.cpp +++ b/backend-v2/tests/codegen/LetNode_test.cpp @@ -46,7 +46,7 @@ static void test_let_basic(void **state) { bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_type( ConstNode_ConstType_constTypeNumber); - // Binding y = 2 + // Binding y = 2N (BigInt) auto *by = let->add_bindings(); by->set_op(opBinding); auto *byc = by->mutable_subnode()->mutable_binding(); @@ -55,6 +55,7 @@ static void test_let_basic(void **state) { byc->mutable_init()->mutable_subnode()->mutable_const_()->set_val("2"); byc->mutable_init()->mutable_subnode()->mutable_const_()->set_type( ConstNode_ConstType_constTypeNumber); + byc->mutable_init()->set_tag("clojure.lang.BigInt"); // Body: (StaticCall rt.Core/Numbers_add x y) auto *body = let->mutable_body(); @@ -96,7 +97,9 @@ static void test_let_basic(void **state) { .get() .address; RTValue result = res.toPtr()(); - assert_int_equal(RT_unboxInt32(result), 3); + assert_int_equal(getType(result), bigIntegerType); + assert_int_equal(mpz_get_si(((BigInteger*)RT_unboxPtr(result))->value), 3); + release(result); } catch (const rt::LanguageException &e) { cout << "Caught LanguageException: " << rt::getExceptionString(e) << endl; assert_true(false); diff --git a/backend-v2/tests/codegen/Overflow_test.cpp b/backend-v2/tests/codegen/Overflow_test.cpp index 962f319..119f3b3 100644 --- a/backend-v2/tests/codegen/Overflow_test.cpp +++ b/backend-v2/tests/codegen/Overflow_test.cpp @@ -177,12 +177,52 @@ static void test_integer_overflow_mul(void **state) { }); } +static void test_bigint_arithmetic(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/add 100 200N) + Node callNode; + callNode.set_op(opStaticCall); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("add"); + + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->set_tag("long"); + arg1->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + arg1->mutable_subnode()->mutable_const_()->set_val("100"); + + auto *arg2 = sc->add_args(); + arg2->set_op(opConst); + arg2->set_tag("clojure.lang.BigInt"); + arg2->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + arg2->mutable_subnode()->mutable_const_()->set_val("200"); + + auto resCall = + engine.compileAST(callNode, "__test_bigint_add").get().address; + + RTValue result = resPtrToValue(resCall); + assert_int_equal(bigIntegerType, getType(result)); + + // Cleanup + release(result); + }); +} + int main(void) { initialise_memory(); 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), + cmocka_unit_test(test_bigint_arithmetic), }; int result = cmocka_run_group_tests(tests, NULL, NULL); diff --git a/backend-v2/tests/codegen/RecurNode_test.cpp b/backend-v2/tests/codegen/RecurNode_test.cpp index cd8a68b..ef491cc 100644 --- a/backend-v2/tests/codegen/RecurNode_test.cpp +++ b/backend-v2/tests/codegen/RecurNode_test.cpp @@ -2,23 +2,56 @@ #include "../../jit/JITEngine.h" #include "../../runtime/Object.h" #include "../../runtime/Function.h" +#include "../../tools/EdnParser.h" +#include "../../state/ThreadsafeCompilerState.h" +#include +#include extern "C" { +#include "../../runtime/RuntimeInterface.h" #include "../../runtime/tests/TestTools.h" #include #include #include } +using namespace std; using namespace rt; using namespace clojure::rt::protobuf::bytecode; -static void test_recur_factorial(void **state) { +static void setup_compiler_state(rt::ThreadsafeCompilerState &compState, + rt::JITEngine &engine) { + Programme astClasses; + { + std::fstream classesInput("tests/rt-classes.cljb", + std::ios::in | std::ios::binary); + if (!astClasses.ParseFromIstream(&classesInput)) { + fail_msg("Failed to parse bytecode."); + } + } + + llvm::orc::ExecutorAddr resClasses = + engine.compileAST(astClasses.nodes(0), "__classes").get().address; + RTValue classes = resClasses.toPtr()(); + auto classesList = rt::buildClasses(classes); + for (auto &desc : classesList) { + auto &nameStr = desc->name; + String *className = String_create(nameStr.c_str()); + Ptr_retain(className); + Class *cls = Class_create(className, className, 0, nullptr); + cls->compilerExtension = desc.release(); + cls->compilerExtensionDestructor = delete_class_description; + compState.classRegistry.registerObject(nameStr.c_str(), cls); + } +} + +static void test_recur_factorial_bigint(void **state) { (void)state; ASSERT_MEMORY_ALL_BALANCED({ rt::JITEngine engine; + setup_compiler_state(engine.threadsafeState, engine); - // (fn [n acc] (if (zero? n) acc (recur (dec n) (* n acc)))) + // (fn [n acc] (if (equiv 0 n) acc (recur (dec n) (* n acc)))) Node fnNode; fnNode.set_op(opFn); auto *fn = fnNode.mutable_subnode()->mutable_fn(); @@ -28,7 +61,6 @@ static void test_recur_factorial(void **state) { mn->set_fixedarity(2); mn->set_loopid("fact_loop"); - // Params: n, acc auto *pn = mn->add_params(); pn->set_op(opBinding); pn->mutable_subnode()->mutable_binding()->set_name("n"); @@ -37,24 +69,26 @@ static void test_recur_factorial(void **state) { pacc->set_op(opBinding); pacc->mutable_subnode()->mutable_binding()->set_name("acc"); - // Body: (if (zero? n) acc (recur (dec n) (* n acc))) auto *body = mn->mutable_body(); body->set_op(opIf); auto *ifn = body->mutable_subnode()->mutable_if_(); - // Test: (zero? n) -> using static call to a hypothetical zero? or just compare - // For simplicity, let's use a dummy static call that we can mock or just use a known one. - // Actually, let's use opLocal for 'n' and see if it's nil? No. - // Let's just use (zero? n) as a static call to ClojureRT.zero? + // Test: (clojure.lang.Util/equiv 0 n) auto *test = ifn->mutable_test(); test->set_op(opStaticCall); auto *sc = test->mutable_subnode()->mutable_staticcall(); - sc->set_classname("clojure.lang.RT"); - sc->set_methodname("isZero"); // Hypothetical - auto *sc_arg = sc->add_args(); - sc_arg->set_op(opLocal); - sc_arg->mutable_subnode()->mutable_local()->set_name("n"); - sc_arg->mutable_subnode()->mutable_local()->set_local(localTypeArg); + sc->set_class_("clojure.lang.Util"); + sc->set_method("equiv"); + + auto *arg0 = sc->add_args(); + arg0->set_op(opConst); + arg0->mutable_subnode()->mutable_const_()->set_val("0"); + arg0->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + + auto *argn = sc->add_args(); + argn->set_op(opLocal); + argn->mutable_subnode()->mutable_local()->set_name("n"); + argn->mutable_subnode()->mutable_local()->set_local(localTypeArg); // Then: acc auto *then = ifn->mutable_then(); @@ -68,23 +102,27 @@ static void test_recur_factorial(void **state) { auto *rn = else_->mutable_subnode()->mutable_recur(); rn->set_loopid("fact_loop"); - // arg1: (dec n) + // arg1: (clojure.lang.Numbers/minus n 1) auto *rarg1 = rn->add_exprs(); rarg1->set_op(opStaticCall); auto *sc1 = rarg1->mutable_subnode()->mutable_staticcall(); - sc1->set_classname("clojure.lang.Numbers"); - sc1->set_methodname("dec"); - auto *sc1_arg = sc1->add_args(); - sc1_arg->set_op(opLocal); - sc1_arg->mutable_subnode()->mutable_local()->set_name("n"); - sc1_arg->mutable_subnode()->mutable_local()->set_local(localTypeArg); - - // arg2: (* n acc) + sc1->set_class_("clojure.lang.Numbers"); + sc1->set_method("minus"); + auto *sc1_arg1 = sc1->add_args(); + sc1_arg1->set_op(opLocal); + sc1_arg1->mutable_subnode()->mutable_local()->set_name("n"); + sc1_arg1->mutable_subnode()->mutable_local()->set_local(localTypeArg); + auto *sc1_arg2 = sc1->add_args(); + sc1_arg2->set_op(opConst); + sc1_arg2->mutable_subnode()->mutable_const_()->set_val("1"); + sc1_arg2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + + // arg2: (clojure.lang.Numbers/multiply n acc) auto *rarg2 = rn->add_exprs(); rarg2->set_op(opStaticCall); auto *sc2 = rarg2->mutable_subnode()->mutable_staticcall(); - sc2->set_classname("clojure.lang.Numbers"); - sc2->set_methodname("multiply"); + sc2->set_class_("clojure.lang.Numbers"); + sc2->set_method("multiply"); auto *sc2_arg1 = sc2->add_args(); sc2_arg1->set_op(opLocal); sc2_arg1->mutable_subnode()->mutable_local()->set_name("n"); @@ -94,22 +132,52 @@ static void test_recur_factorial(void **state) { sc2_arg2->mutable_subnode()->mutable_local()->set_name("acc"); sc2_arg2->mutable_subnode()->mutable_local()->set_local(localTypeArg); - // Now compile. We need to define those static methods in the engine or mock them. - // For this test, we can just compile and check IR if possible, - // or actually run it if we have clojure.lang.Numbers etc available. - // Since we are in a unit test, maybe we just verify it compiles and look for musttail. + // (let [fact (fn [n acc] ...)] (fact 5 1N)) + Node letNode; + letNode.set_op(opLet); + auto *let = letNode.mutable_subnode()->mutable_let(); + + auto *bf = let->add_bindings(); + bf->set_op(opBinding); + bf->mutable_subnode()->mutable_binding()->set_name("fact"); + bf->mutable_subnode()->mutable_binding()->mutable_init()->CopyFrom(fnNode); + + auto *letBody = let->mutable_body(); + letBody->set_op(opInvoke); + auto *inv = letBody->mutable_subnode()->mutable_invoke(); + inv->mutable_fn()->set_op(opLocal); + inv->mutable_fn()->mutable_subnode()->mutable_local()->set_name("fact"); + inv->mutable_fn()->mutable_subnode()->mutable_local()->set_local(localTypeLet); + + // arg1: 5 + auto *arg1 = inv->add_args(); + arg1->set_op(opConst); + arg1->mutable_subnode()->mutable_const_()->set_val("5"); + arg1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + + // arg2: 1N + auto *arg2 = inv->add_args(); + arg2->set_op(opConst); + arg2->mutable_subnode()->mutable_const_()->set_val("1"); + arg2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg2->set_tag("clojure.lang.BigInt"); + + auto res = engine.compileAST(letNode, "recur_fact_test").get(); + RTValue (*func)() = res.address.toPtr(); + RTValue result = func(); - auto res = engine.compileAST(fnNode, "recur_test").get(); + assert_int_equal(bigIntegerType, getType(result)); + BigInteger *resultBI = (BigInteger *)RT_unboxPtr(result); + assert_int_equal(120, mpz_get_si(resultBI->value)); - // If it compiles successfully, we are halfway there. - assert_non_null(res.address.toPtr()); + release(result); }); } int main(void) { initialise_memory(); const struct CMUnitTest tests[] = { - cmocka_unit_test(test_recur_factorial), + cmocka_unit_test(test_recur_factorial_bigint), }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/backend-v2/tests/codegen/VariadicInvoke_test.cpp b/backend-v2/tests/codegen/VariadicInvoke_test.cpp new file mode 100644 index 0000000..296e5ae --- /dev/null +++ b/backend-v2/tests/codegen/VariadicInvoke_test.cpp @@ -0,0 +1,240 @@ +#include "../../codegen/CodeGen.h" +#include "../../jit/JITEngine.h" +#include "../../runtime/Ebr.h" +#include "../../runtime/Numbers.h" +#include "../../runtime/String.h" +#include "../../tools/EdnParser.h" +#include + +extern "C" { +#include "../../runtime/tests/TestTools.h" +#include +} + +using namespace rt; +using namespace clojure::rt::protobuf::bytecode; + +static void delete_class_description(void *ptr) { + delete static_cast(ptr); +} + +static void setup_minimal_runtime(JITEngine &engine) { + auto &compState = engine.threadsafeState; + + { + String *nameStr = String_create("clojure.lang.Numbers"); + Ptr_retain(nameStr); + ::Class *cls = Class_create(nameStr, nameStr, 0, nullptr); + rt::ClassDescription *ext = new rt::ClassDescription(); + ext->name = "clojure.lang.Numbers"; + cls->compilerExtension = ext; + cls->compilerExtensionDestructor = ::delete_class_description; + compState.classRegistry.registerObject("clojure.lang.Numbers", cls); + } + + { + String *nameStr = String_create("java.math.BigInteger"); + Ptr_retain(nameStr); + ::Class *cls = Class_create(nameStr, nameStr, 0, nullptr); + rt::ClassDescription *ext = new rt::ClassDescription(); + ext->name = "java.math.BigInteger"; + cls->compilerExtension = ext; + cls->compilerExtensionDestructor = ::delete_class_description; + compState.classRegistry.registerObject("java.math.BigInteger", cls); + } +} + +static void test_variadic_basic(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + JITEngine engine(llvm::OptimizationLevel::O0, false); + setup_minimal_runtime(engine); + + Node fnNode; + fnNode.set_op(opFn); + auto *fn = fnNode.mutable_subnode()->mutable_fn(); + fn->set_isvariadic(true); + auto *m = fn->add_methods(); + auto *mn = m->mutable_subnode()->mutable_fnmethod(); + mn->set_fixedarity(1); + mn->set_isvariadic(true); + mn->add_params()->mutable_subnode()->mutable_binding()->set_name("x__#0"); + mn->add_params()->mutable_subnode()->mutable_binding()->set_name( + "args__#0"); + auto *body = mn->mutable_body(); + body->set_op(opLocal); + body->mutable_subnode()->mutable_local()->set_name("args__#0"); + body->mutable_subnode()->mutable_local()->set_local(localTypeArg); + + auto *gBodyX = body->add_dropmemory(); + gBodyX->set_variablename("x__#0"); + gBodyX->set_requiredrefcountchange(-1); + + auto *uBodyArgs = body->add_unwindmemory(); + uBodyArgs->set_variablename("args__#0"); + uBodyArgs->set_requiredrefcountchange(-1); + + Node root; + root.set_op(opLet); + auto *let = root.mutable_subnode()->mutable_let(); + auto *bf = let->add_bindings(); + bf->set_op(opBinding); + auto *bnF = bf->mutable_subnode()->mutable_binding(); + bnF->set_name("f__#0"); + bnF->mutable_init()->CopyFrom(fnNode); + + for (int i = 1; i <= 3; ++i) { + auto *ba = let->add_bindings(); + ba->set_op(opBinding); + auto *b = ba->mutable_subnode()->mutable_binding(); + b->set_name("v" + std::to_string(i) + "__#0"); + b->mutable_init()->set_op(opConst); + b->mutable_init()->mutable_subnode()->mutable_const_()->set_val( + std::to_string(i)); + b->mutable_init()->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + } + + auto *lb = let->mutable_body(); + lb->set_op(opDo); + auto *do_ = lb->mutable_subnode()->mutable_do_(); + + auto *invNode = do_->mutable_ret(); + invNode->set_op(opInvoke); + auto *inv = invNode->mutable_subnode()->mutable_invoke(); + inv->mutable_fn()->set_op(opLocal); + inv->mutable_fn()->mutable_subnode()->mutable_local()->set_name("f__#0"); + inv->mutable_fn()->mutable_subnode()->mutable_local()->set_local( + localTypeLet); + + auto *uFn = inv->mutable_fn()->add_unwindmemory(); + uFn->set_variablename("f__#0"); + uFn->set_requiredrefcountchange(-1); + + for (int i = 1; i <= 3; ++i) { + auto *a = inv->add_args(); + a->set_op(opLocal); + a->mutable_subnode()->mutable_local()->set_name("v" + std::to_string(i) + + "__#0"); + a->mutable_subnode()->mutable_local()->set_local(localTypeLet); + + auto *uArg = a->add_unwindmemory(); + uArg->set_variablename("f__#0"); + uArg->set_requiredrefcountchange(-1); + } + + // No drop-memory for f__#0 or vN here because they are consumed by the + // InvokeNode backend and the variadic pack respectively. + + auto res = engine.compileAST(root, "test1").get(); + RTValue (*func)() = res.address.toPtr(); + RTValue result = func(); + + assert_int_equal(persistentListType, getType(result)); + PersistentList *list = (PersistentList *)RT_unboxPtr(result); + assert_int_equal(2, RT_unboxInt32(list->first)); + assert_int_equal(3, RT_unboxInt32(list->rest->first)); + release(result); + }); +} + +static void test_variadic_only_bigints(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + JITEngine engine(llvm::OptimizationLevel::O0, false); + + Node fnNode; + fnNode.set_op(opFn); + auto *fn = fnNode.mutable_subnode()->mutable_fn(); + fn->set_isvariadic(true); + auto *m = fn->add_methods(); + auto *mn = m->mutable_subnode()->mutable_fnmethod(); + mn->set_fixedarity(0); + mn->set_isvariadic(true); + mn->add_params()->mutable_subnode()->mutable_binding()->set_name( + "args__#0"); + mn->mutable_body()->set_op(opLocal); + mn->mutable_body()->mutable_subnode()->mutable_local()->set_name( + "args__#0"); + mn->mutable_body()->mutable_subnode()->mutable_local()->set_local( + localTypeArg); + + auto *uBodyArgs = mn->mutable_body()->add_unwindmemory(); + uBodyArgs->set_variablename("args__#0"); + uBodyArgs->set_requiredrefcountchange(-1); + + Node root; + root.set_op(opLet); + auto *let = root.mutable_subnode()->mutable_let(); + auto *bf = let->add_bindings(); + bf->set_op(opBinding); + auto *bnF = bf->mutable_subnode()->mutable_binding(); + bnF->set_name("f__#0"); + bnF->mutable_init()->CopyFrom(fnNode); + + for (int i = 0; i < 5; ++i) { + auto *ba = let->add_bindings(); + ba->set_op(opBinding); + auto *b = ba->mutable_subnode()->mutable_binding(); + b->set_name("arg" + std::to_string(i) + "__#0"); + b->mutable_init()->set_op(opConst); + b->mutable_init()->mutable_subnode()->mutable_const_()->set_val( + std::to_string(i)); + b->mutable_init()->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + b->mutable_init()->set_tag("clojure.lang.BigInt"); + } + + auto *lb = let->mutable_body(); + lb->set_op(opDo); + auto *do_ = lb->mutable_subnode()->mutable_do_(); + auto *invNode = do_->mutable_ret(); + invNode->set_op(opInvoke); + auto *inv = invNode->mutable_subnode()->mutable_invoke(); + inv->mutable_fn()->set_op(opLocal); + inv->mutable_fn()->mutable_subnode()->mutable_local()->set_name("f__#0"); + inv->mutable_fn()->mutable_subnode()->mutable_local()->set_local( + localTypeLet); + + auto *uFn = inv->mutable_fn()->add_unwindmemory(); + uFn->set_variablename("f__#0"); + uFn->set_requiredrefcountchange(-1); + + for (int i = 0; i < 5; ++i) { + auto *a = inv->add_args(); + a->set_op(opLocal); + a->mutable_subnode()->mutable_local()->set_name( + "arg" + std::to_string(i) + "__#0"); + a->mutable_subnode()->mutable_local()->set_local(localTypeLet); + + auto *uArg = a->add_unwindmemory(); + uArg->set_variablename("f__#0"); + uArg->set_requiredrefcountchange(-1); + } + + // Consumption handled by backend and bouncer. + + auto res = engine.compileAST(root, "test2").get(); + RTValue (*func)() = res.address.toPtr(); + RTValue result = func(); + + assert_int_equal(persistentListType, getType(result)); + PersistentList *curr = (PersistentList *)RT_unboxPtr(result); + for (int i = 0; i < 5; ++i) { + assert_int_equal(bigIntegerType, getType(curr->first)); + assert_int_equal( + i, mpz_get_si(((BigInteger *)RT_unboxPtr(curr->first))->value)); + curr = curr->rest; + } + release(result); + }); +} + +int main(void) { + initialise_memory(); + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_variadic_basic), + cmocka_unit_test(test_variadic_only_bigints), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/backend-v2/tools/ThreadPool.cpp b/backend-v2/tools/ThreadPool.cpp index 0b1777c..889104a 100644 --- a/backend-v2/tools/ThreadPool.cpp +++ b/backend-v2/tools/ThreadPool.cpp @@ -7,7 +7,7 @@ ThreadPool::ThreadPool(size_t threads, Priority priority) : stop(false) { for (size_t i = 0; i < threads; ++i) workers.emplace_back([this, priority] { Ebr_register_thread(); - Ebr_enter_critical(); + if (priority == Priority::Low) this->setLowPriority(); for (;;) { @@ -17,7 +17,6 @@ ThreadPool::ThreadPool(size_t threads, Priority priority) : stop(false) { this->condition.wait( lock, [this] { return this->stop || !this->tasks.empty(); }); if (this->stop && this->tasks.empty()) { - Ebr_leave_critical(); Ebr_unregister_thread(); return; } @@ -25,8 +24,9 @@ ThreadPool::ThreadPool(size_t threads, Priority priority) : stop(false) { task = std::move(this->tasks.front()); this->tasks.pop(); } + Ebr_enter_critical(); task(); - Ebr_flush_critical(); + Ebr_leave_critical(); } }); }