diff --git a/README.md b/README.md index 89dff3a9..39ece4dd 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,10 @@ To debug leaks on OS X: leaks --atExit -- ./name_of_the_test +To show llvm IR for runtime: + +llvm-dis runtime_uber.bc -o runtime_uber.ll + ## Running diff --git a/backend-v2/CMakeLists.txt b/backend-v2/CMakeLists.txt index 26f6b7f1..9301a63b 100644 --- a/backend-v2/CMakeLists.txt +++ b/backend-v2/CMakeLists.txt @@ -31,7 +31,6 @@ set(CMAKE_CXX_FLAGS_RELEASE "-Ofast") set(CMAKE_CXX_STANDARD 20) add_compile_definitions(BUILD_TYPE="${CMAKE_BUILD_TYPE}") -add_subdirectory(runtime) # --- Dependency Discovery --- @@ -138,6 +137,8 @@ set(BACKEND_SOURCES codegen/ops/LocalNode.cpp codegen/ops/WithMetaNode.cpp) +add_subdirectory(runtime) + # 2. Protobuf Detection find_package(Protobuf CONFIG QUIET) diff --git a/backend-v2/codegen/CodeGen.cpp b/backend-v2/codegen/CodeGen.cpp index 72d846a5..98c7c91e 100644 --- a/backend-v2/codegen/CodeGen.cpp +++ b/backend-v2/codegen/CodeGen.cpp @@ -1,11 +1,11 @@ #include "CodeGen.h" -#include "CleanupChainGuard.h" #include "../bridge/Exceptions.h" #include "../cljassert.h" +#include "CleanupChainGuard.h" #include "TypedValue.h" +#include "runtime/Object.h" #include "runtime/RTValue.h" #include "runtime/String.h" -#include "runtime/Object.h" using namespace llvm; using namespace clojure::rt::protobuf::bytecode; @@ -67,7 +67,8 @@ std::string CodeGen::codegenTopLevel(const Node &node) { // Declare personality function FunctionType *personalityFnTy = FunctionType::get(types.i32Ty, true); - personalityFn = TheModule->getOrInsertFunction("__gxx_personality_v0", personalityFnTy); + personalityFn = + TheModule->getOrInsertFunction("__gxx_personality_v0", personalityFnTy); F->setPersonalityFn(cast(personalityFn.getCallee())); // Set initial debug location @@ -83,10 +84,11 @@ std::string CodeGen::codegenTopLevel(const Node &node) { } TypedValue CodeGen::codegen(const Node &node, - const ObjectTypeSet &typeRestrictions) { + const ObjectTypeSet &typeRestrictions) { CLJ_ASSERT(TSContext != nullptr, "Codegen was moved"); - MemoryManagement::UnwindGuidanceGuard guidanceGuard(memoryManagement, &node.unwindmemory()); + MemoryManagement::UnwindGuidanceGuard guidanceGuard(memoryManagement, + &node.unwindmemory()); auto env = node.env(); if (!LexicalBlocks.empty()) { @@ -137,8 +139,8 @@ TypedValue CodeGen::codegen(const Node &node, return codegen(node, node.subnode().if_(), typeRestrictions); // case opImport: // return codegen(node, node.subnode().import(), typeRestrictions); - // case opInstanceCall: - // return codegen(node, node.subnode().instancecall(), typeRestrictions); + //case opInstanceCall: +// return codegen(node, node.subnode().instancecall(), typeRestrictions); // case opInstanceField: // return codegen(node, node.subnode().instancefield(), typeRestrictions); // case opIsInstance: @@ -237,8 +239,8 @@ ObjectTypeSet CodeGen::getType(const Node &node, return getType(node, node.subnode().if_(), typeRestrictions); // case opImport: // return getType(node, node.subnode().import(), typeRestrictions); - // case opInstanceCall: - // return getType(node, node.subnode().instancecall(), typeRestrictions); + //case opInstanceCall: +// return getType(node, node.subnode().instancecall(), typeRestrictions); // case opInstanceField: // return getType(node, node.subnode().instancefield(), typeRestrictions); // case opIsInstance: @@ -299,7 +301,8 @@ ObjectTypeSet CodeGen::getType(const Node &node, Var *CodeGen::getOrCreateVar(std::string_view name) { return compilerState.varRegistry.getOrCreate( std::string(name).c_str(), [name]() { - RTValue keyword = Keyword_create(String_createDynamicStr(std::string(name).c_str())); + RTValue keyword = + Keyword_create(String_createDynamicStr(std::string(name).c_str())); return Var_create(keyword); }); } @@ -314,11 +317,12 @@ bool CodeGen::canThrow(const Node &node) { case opFn: case opFnMethod: return false; - + case opLet: { const auto &l = node.subnode().let(); for (int i = 0; i < l.bindings_size(); ++i) { - if (canThrow(l.bindings(i))) return true; + if (canThrow(l.bindings(i))) + return true; } return canThrow(l.body()); } @@ -394,5 +398,4 @@ bool CodeGen::canThrow(const Node &node) { } } - } // namespace rt diff --git a/backend-v2/codegen/CodeGen.h b/backend-v2/codegen/CodeGen.h index 9a0dd79e..b87654cf 100644 --- a/backend-v2/codegen/CodeGen.h +++ b/backend-v2/codegen/CodeGen.h @@ -58,7 +58,7 @@ class CodeGen { std::unique_ptr DIB; llvm::DICompileUnit *CU; std::vector LexicalBlocks; - + // Static Landing Pads management via manager llvm::FunctionCallee personalityFn; @@ -122,6 +122,8 @@ class CodeGen { const ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, const LocalNode &subnode, const ObjectTypeSet &typeRestrictions); + TypedValue codegen(const Node &node, const InstanceCallNode &subnode, + const ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, const ObjectTypeSet &typeRestrictions); @@ -154,15 +156,22 @@ class CodeGen { const ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, const LocalNode &subnode, const ObjectTypeSet &typeRestrictions); + ObjectTypeSet getType(const Node &node, const InstanceCallNode &subnode, + const ObjectTypeSet &typeRestrictions); + Var *getOrCreateVar(std::string_view name); bool canThrow(const clojure::rt::protobuf::bytecode::Node &node); - + // Exception safety helpers void pushResource(TypedValue val) { memoryManagement.pushResource(val); } void popResource() { memoryManagement.popResource(); } - llvm::BasicBlock* getLandingPad(size_t skipCount = 0) { return memoryManagement.getLandingPad(skipCount); } + llvm::BasicBlock *getLandingPad(size_t skipCount = 0) { + return memoryManagement.getLandingPad(skipCount); + } bool hasLandingPad() const { return memoryManagement.hasPushedResources(); } - bool hasPushedResources() const { return memoryManagement.hasPushedResources(); } + bool hasPushedResources() const { + return memoryManagement.hasPushedResources(); + } MemoryManagement &getMemoryManagement() { return memoryManagement; } }; diff --git a/backend-v2/codegen/MemoryManagement.cpp b/backend-v2/codegen/MemoryManagement.cpp index 7f86e188..f10dc23e 100644 --- a/backend-v2/codegen/MemoryManagement.cpp +++ b/backend-v2/codegen/MemoryManagement.cpp @@ -26,7 +26,7 @@ MemoryManagement::MemoryManagement(llvm::LLVMContext &c, IRBuilder<> &b, LLVMTypes &t, VariableBindings &vb, InvokeManager &i) - : context(c), builder(b), theModule(m), valueEncoder(v), types(t), + : context(c), builder(b), valueEncoder(v), types(t), variableBindingStack(vb), invoke(i) {} void MemoryManagement::initFunction(llvm::Function *F) { @@ -183,7 +183,7 @@ void MemoryManagement::dynamicMemoryGuidance( auto name = guidance.variablename(); auto change = guidance.requiredrefcountchange(); - for (word_t depth = variableBindingStack.stackDepth() - 1; depth >= 0; + for (word_t depth = (word_t)variableBindingStack.stackDepth() - 1; depth >= 0; depth--) { auto val = variableBindingStack.find(name, depth); if (val) { diff --git a/backend-v2/codegen/MemoryManagement.h b/backend-v2/codegen/MemoryManagement.h index faa18e62..b8e418bc 100644 --- a/backend-v2/codegen/MemoryManagement.h +++ b/backend-v2/codegen/MemoryManagement.h @@ -66,7 +66,7 @@ class MemoryManagement { private: llvm::LLVMContext &context; llvm::IRBuilder<> &builder; - llvm::Module &theModule; + ValueEncoder &valueEncoder; LLVMTypes &types; VariableBindings &variableBindingStack; diff --git a/backend-v2/codegen/invoke/InvokeManager.cpp b/backend-v2/codegen/invoke/InvokeManager.cpp index a220e17b..83746aba 100644 --- a/backend-v2/codegen/invoke/InvokeManager.cpp +++ b/backend-v2/codegen/invoke/InvokeManager.cpp @@ -118,7 +118,7 @@ TypedValue InvokeManager::generateIntrinsic(const IntrinsicDescription &id, if (id.type == CallType::Intrinsic) { auto git = genericIntrinsics.find(id.symbol); if (git != genericIntrinsics.end()) { - return TypedValue(id.returnType, git->second(builder, args)); + return TypedValue(id.returnType, git->second(builder, args, guard)); } auto it = intrinsics.find(id.symbol); diff --git a/backend-v2/codegen/invoke/InvokeManager.h b/backend-v2/codegen/invoke/InvokeManager.h index 5e589ac5..aa3ac911 100644 --- a/backend-v2/codegen/invoke/InvokeManager.h +++ b/backend-v2/codegen/invoke/InvokeManager.h @@ -29,7 +29,7 @@ using IntrinsicCall = std::function &, using TypeIntrinsicCall = std::function &)>; using GenericIntrinsicCall = std::function &, const std::vector &)>; + llvm::IRBuilder<> &, const std::vector &, CleanupChainGuard *)>; class InvokeManager { friend void registerMathIntrinsics(InvokeManager &mgr); diff --git a/backend-v2/codegen/invoke/InvokeManager_Math.cpp b/backend-v2/codegen/invoke/InvokeManager_Math.cpp index 0d7ed6cd..1a2f799c 100644 --- a/backend-v2/codegen/invoke/InvokeManager_Math.cpp +++ b/backend-v2/codegen/invoke/InvokeManager_Math.cpp @@ -1,9 +1,6 @@ -#include "InvokeManager.h" -#include "../../types/ConstantBool.h" #include "../../types/ConstantDouble.h" #include "../../types/ConstantInteger.h" -#include "../../types/ConstantBigInteger.h" -#include "../../types/ConstantRatio.h" +#include "InvokeManager.h" #include "llvm/IR/MDBuilder.h" using namespace llvm; @@ -11,42 +8,6 @@ using namespace std; namespace rt { -static Value *coerceToDouble(InvokeManager &mgr, IRBuilder<> &b, - LLVMTypes &types, ValueEncoder &valueEncoder, - const TypedValue &tv) { - if (tv.type.isUnboxedType(doubleType)) - return tv.value; - if (tv.type.isUnboxedType(integerType)) - return b.CreateSIToFP(tv.value, types.doubleTy, "conv_d"); - if (tv.type.isBoxedType(doubleType)) - return valueEncoder.unboxDouble(tv).value; - if (tv.type.isBoxedType(integerType)) { - Value *i = valueEncoder.unboxInt32(tv).value; - return b.CreateSIToFP(i, types.doubleTy, "conv_d"); - } - - // Handle BigInt/Ratio which are usually unboxed pointers here - if (tv.type.isDetermined()) { - if (tv.type.determinedType() == bigIntegerType) { - ObjectTypeSet dT(doubleType); - return mgr - .invokeRuntime("BigInteger_toDouble", &dT, - {ObjectTypeSet(bigIntegerType)}, {tv}) - .value; - } - if (tv.type.determinedType() == ratioType) { - ObjectTypeSet dT(doubleType); - return mgr - .invokeRuntime("Ratio_toDouble", &dT, {ObjectTypeSet(ratioType)}, {tv}) - .value; - } - } - - // Fallback for undetermined or generic types: call runtime Numbers_toDouble - ObjectTypeSet dT(doubleType); - return mgr.invokeRuntime("Numbers_toDouble", &dT, {ObjectTypeSet::all()}, {tv}).value; -} - void registerMathIntrinsics(InvokeManager &mgr) { auto &typeIntrinsics = mgr.typeIntrinsics; auto &intrinsics = mgr.intrinsics; @@ -55,10 +16,40 @@ void registerMathIntrinsics(InvokeManager &mgr) { auto &valueEncoder = mgr.valueEncoder; auto &theModule = mgr.theModule; - auto regUnaryMath = [&](const string &intrinsicName, const string &stdName) { - genericIntrinsics[intrinsicName] = [&mgr, &types, &valueEncoder, - stdName](auto &b, auto &args) { - Value *val = coerceToDouble(mgr, b, types, valueEncoder, args[0]); + auto regUnaryMath = [&](const string &intrinsicName, + const string &wrapperName) { + genericIntrinsics[intrinsicName] = + [&mgr, wrapperName](auto &b, auto &args, CleanupChainGuard *guard) { + ObjectTypeSet dT(doubleType); + return mgr + .invokeRuntime(wrapperName, &dT, {ObjectTypeSet::all()}, + {args[0]}, false, guard) + .value; + }; + }; + + auto regBinaryMath = [&](const string &intrinsicName, + const string &wrapperName) { + genericIntrinsics[intrinsicName] = + [&mgr, wrapperName](auto &b, auto &args, CleanupChainGuard *guard) { + ObjectTypeSet dT(doubleType); + return mgr + .invokeRuntime(wrapperName, &dT, + {ObjectTypeSet::all(), ObjectTypeSet::all()}, + {args[0], args[1]}, false, guard) + .value; + }; + }; + + auto regUnarySpecialized = [&](const string &intrinsicName, + const string &stdName, bool fromInt) { + intrinsics[intrinsicName] = [&mgr, &types, stdName, + fromInt](llvm::IRBuilder<> &b, + std::vector args) { + Value *val = args[0]; + if (fromInt) { + val = b.CreateSIToFP(val, types.doubleTy, "conv_d"); + } FunctionType *ft = FunctionType::get(types.doubleTy, {types.doubleTy}, false); FunctionCallee fn = mgr.theModule.getOrInsertFunction(stdName, ft); @@ -66,121 +57,179 @@ void registerMathIntrinsics(InvokeManager &mgr) { }; }; - auto regBinaryMath = [&](const string &intrinsicName, const string &stdName) { - genericIntrinsics[intrinsicName] = [&mgr, &types, &valueEncoder, - stdName](auto &b, auto &args) { - Value *v1 = coerceToDouble(mgr, b, types, valueEncoder, args[0]); - Value *v2 = coerceToDouble(mgr, b, types, valueEncoder, args[1]); - FunctionType *ft = - FunctionType::get(types.doubleTy, {types.doubleTy, types.doubleTy}, false); + auto regBinarySpecialized = [&](const string &intrinsicName, + const string &stdName, bool fromIntLeft, + bool fromIntRight) { + intrinsics[intrinsicName] = [&mgr, &types, stdName, fromIntLeft, + fromIntRight](llvm::IRBuilder<> &b, + std::vector args) { + Value *val1 = args[0]; + Value *val2 = args[1]; + if (fromIntLeft) { + val1 = b.CreateSIToFP(val1, types.doubleTy, "conv_d"); + } + if (fromIntRight) { + val2 = b.CreateSIToFP(val2, types.doubleTy, "conv_d"); + } + FunctionType *ft = FunctionType::get( + types.doubleTy, {types.doubleTy, types.doubleTy}, false); FunctionCallee fn = mgr.theModule.getOrInsertFunction(stdName, ft); - return b.CreateCall(fn, {v1, v2}); + return b.CreateCall(fn, {val1, val2}); }; }; - regUnaryMath("Math_sin", "sin"); - regUnaryMath("Math_cos", "cos"); - regUnaryMath("Math_tan", "tan"); - regUnaryMath("Math_asin", "asin"); - regUnaryMath("Math_acos", "acos"); - regUnaryMath("Math_atan", "atan"); - regUnaryMath("Math_exp", "exp"); - regUnaryMath("Math_exp2", "exp2"); - regUnaryMath("Math_exp10", "exp10"); - regUnaryMath("Math_log", "log"); - regUnaryMath("Math_log10", "log10"); - regUnaryMath("Math_logb", "logb"); - regUnaryMath("Math_log2", "log2"); - regUnaryMath("Math_sqrt", "sqrt"); - regUnaryMath("Math_cbrt", "cbrt"); - regUnaryMath("Math_exp1m", "exp1m"); - regUnaryMath("Math_log1p", "log1p"); - regUnaryMath("Math_abs", "fabs"); - - regBinaryMath("Math_atan2", "atan2"); - regBinaryMath("Math_pow", "pow"); - regBinaryMath("Math_hypot", "hypot"); + auto regMath = [&](const string &name, const string &stdName, + bool binary = false) { + if (binary) { + regBinaryMath("Math_" + name, "Numbers_" + name); + regBinarySpecialized("Math_" + name + "_DD", stdName, false, false); + regBinarySpecialized("Math_" + name + "_DI", stdName, false, true); + regBinarySpecialized("Math_" + name + "_ID", stdName, true, false); + regBinarySpecialized("Math_" + name + "_II", stdName, true, true); + } else { + regUnaryMath("Math_" + name, "Numbers_" + name); + regUnarySpecialized("Math_" + name + "_D", stdName, false); + regUnarySpecialized("Math_" + name + "_I", stdName, true); + } + }; + + regMath("sin", "sin"); + regMath("cos", "cos"); + regMath("tan", "tan"); + regMath("asin", "asin"); + regMath("acos", "acos"); + regMath("atan", "atan"); + regMath("exp", "exp"); + regMath("exp2", "exp2"); + regMath("exp10", "exp10"); + regMath("log", "log"); + regMath("log10", "log10"); + regMath("logb", "logb"); + regMath("log2", "log2"); + regMath("sqrt", "sqrt"); + regMath("cbrt", "cbrt"); + regMath("exp1m", "exp1m"); + regMath("log1p", "log1p"); + regMath("abs", "fabs"); + + regMath("atan2", "atan2", true); + regMath("pow", "pow", true); + regMath("hypot", "hypot", true); // --- Folding --- - typeIntrinsics["Add"] = [](const vector &args) -> ObjectTypeSet { - if (args.size() != 2) return ObjectTypeSet::dynamicType(); + typeIntrinsics["Add"] = + [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) + return ObjectTypeSet::dynamicType(); auto *c1 = args[0].getConstant(); auto *c2 = args[1].getConstant(); auto *i1 = dynamic_cast(c1); auto *i2 = dynamic_cast(c2); - if (i1 && i2) return ObjectTypeSet(integerType, false, new ConstantInteger(i1->value + i2->value)); + if (i1 && i2) + return ObjectTypeSet(integerType, false, + new ConstantInteger(i1->value + i2->value)); return ObjectTypeSet(integerType, false); }; - typeIntrinsics["Sub"] = [](const vector &args) -> ObjectTypeSet { - if (args.size() != 2) return ObjectTypeSet::dynamicType(); + typeIntrinsics["Sub"] = + [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) + return ObjectTypeSet::dynamicType(); auto *c1 = args[0].getConstant(); auto *c2 = args[1].getConstant(); auto *i1 = dynamic_cast(c1); auto *i2 = dynamic_cast(c2); - if (i1 && i2) return ObjectTypeSet(integerType, false, new ConstantInteger(i1->value - i2->value)); + if (i1 && i2) + return ObjectTypeSet(integerType, false, + new ConstantInteger(i1->value - i2->value)); return ObjectTypeSet(integerType, false); }; - typeIntrinsics["Mul"] = [](const vector &args) -> ObjectTypeSet { - if (args.size() != 2) return ObjectTypeSet::dynamicType(); + typeIntrinsics["Mul"] = + [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) + return ObjectTypeSet::dynamicType(); auto *c1 = args[0].getConstant(); auto *c2 = args[1].getConstant(); auto *i1 = dynamic_cast(c1); auto *i2 = dynamic_cast(c2); - if (i1 && i2) return ObjectTypeSet(integerType, false, new ConstantInteger(i1->value * i2->value)); + if (i1 && i2) + return ObjectTypeSet(integerType, false, + new ConstantInteger(i1->value * i2->value)); return ObjectTypeSet(integerType, false); }; - typeIntrinsics["Div"] = [](const vector &args) -> ObjectTypeSet { - if (args.size() != 2) return ObjectTypeSet::dynamicType(); + typeIntrinsics["Div"] = + [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) + return ObjectTypeSet::dynamicType(); auto *c1 = args[0].getConstant(); auto *c2 = args[1].getConstant(); auto *i1 = dynamic_cast(c1); auto *i2 = dynamic_cast(c2); - if (i1 && i2 && i2->value != 0) return ObjectTypeSet(integerType, false, new ConstantInteger(i1->value / i2->value)); + if (i1 && i2 && i2->value != 0) + return ObjectTypeSet(integerType, false, + new ConstantInteger(i1->value / i2->value)); return ObjectTypeSet(integerType, false); }; - typeIntrinsics["FAdd"] = [](const vector &args) -> ObjectTypeSet { - if (args.size() != 2) return ObjectTypeSet::dynamicType(); + typeIntrinsics["FAdd"] = + [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) + return ObjectTypeSet::dynamicType(); auto *c1 = args[0].getConstant(); auto *c2 = args[1].getConstant(); auto *d1 = dynamic_cast(c1); auto *d2 = dynamic_cast(c2); - if (d1 && d2) return ObjectTypeSet(doubleType, false, new ConstantDouble(d1->value + d2->value)); + if (d1 && d2) + return ObjectTypeSet(doubleType, false, + new ConstantDouble(d1->value + d2->value)); return ObjectTypeSet(doubleType, false); }; - typeIntrinsics["FSub"] = [](const vector &args) -> ObjectTypeSet { - if (args.size() != 2) return ObjectTypeSet::dynamicType(); + typeIntrinsics["FSub"] = + [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) + return ObjectTypeSet::dynamicType(); auto *c1 = args[0].getConstant(); auto *c2 = args[1].getConstant(); auto *d1 = dynamic_cast(c1); auto *d2 = dynamic_cast(c2); - if (d1 && d2) return ObjectTypeSet(doubleType, false, new ConstantDouble(d1->value - d2->value)); + if (d1 && d2) + return ObjectTypeSet(doubleType, false, + new ConstantDouble(d1->value - d2->value)); return ObjectTypeSet(doubleType, false); }; - typeIntrinsics["FMul"] = [](const vector &args) -> ObjectTypeSet { - if (args.size() != 2) return ObjectTypeSet::dynamicType(); + typeIntrinsics["FMul"] = + [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) + return ObjectTypeSet::dynamicType(); auto *c1 = args[0].getConstant(); auto *c2 = args[1].getConstant(); auto *d1 = dynamic_cast(c1); auto *d2 = dynamic_cast(c2); - if (d1 && d2) return ObjectTypeSet(doubleType, false, new ConstantDouble(d1->value * d2->value)); + if (d1 && d2) + return ObjectTypeSet(doubleType, false, + new ConstantDouble(d1->value * d2->value)); return ObjectTypeSet(doubleType, false); }; - typeIntrinsics["FDiv"] = [](const vector &args) -> ObjectTypeSet { - if (args.size() != 2) return ObjectTypeSet::dynamicType(); + typeIntrinsics["FDiv"] = + [](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) + return ObjectTypeSet::dynamicType(); auto *c1 = args[0].getConstant(); auto *c2 = args[1].getConstant(); auto *d1 = dynamic_cast(c1); auto *d2 = dynamic_cast(c2); - if (d1 && d2 && d2->value != 0.0) return ObjectTypeSet(doubleType, false, new ConstantDouble(d1->value / d2->value)); + if (d1 && d2 && d2->value != 0.0) + return ObjectTypeSet(doubleType, false, + new ConstantDouble(d1->value / d2->value)); return ObjectTypeSet(doubleType, false); }; // BigInt pure math folding auto regZMath = [&](const string &symbol, auto op) { - typeIntrinsics[symbol] = [&mgr, op](const vector &args) -> ObjectTypeSet { - if (args.size() != 2) return ObjectTypeSet(bigIntegerType, false); + typeIntrinsics[symbol] = + [&mgr, op](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) + return ObjectTypeSet(bigIntegerType, false); mpz_ptr p1 = mgr.getZ(args[0]); mpz_ptr p2 = mgr.getZ(args[1]); if (p1 && p2) { @@ -188,46 +237,71 @@ void registerMathIntrinsics(InvokeManager &mgr) { mpz_init(res); op(res, p1, p2); auto ret = mgr.createZ(res); - mpz_clear(p1); mpz_clear(p2); mpz_clear(res); - free(p1); free(p2); + mpz_clear(p1); + mpz_clear(p2); + mpz_clear(res); + free(p1); + free(p2); return ret; } - if (p1) { mpz_clear(p1); free(p1); } - if (p2) { mpz_clear(p2); free(p2); } + if (p1) { + mpz_clear(p1); + free(p1); + } + if (p2) { + mpz_clear(p2); + free(p2); + } return ObjectTypeSet(bigIntegerType, false); }; }; regZMath("BigInteger_add", mpz_add); regZMath("BigInteger_sub", mpz_sub); regZMath("BigInteger_mul", mpz_mul); - - typeIntrinsics["BigInteger_div"] = [&mgr](const vector &args) -> ObjectTypeSet { - if (args.size() != 2) return ObjectTypeSet::dynamicType(); + + typeIntrinsics["BigInteger_div"] = + [&mgr](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) + return ObjectTypeSet::dynamicType(); mpq_ptr p1 = mgr.getQ(args[0]); mpq_ptr p2 = mgr.getQ(args[1]); if (p1 && p2) { - if (mpq_sgn(p2) == 0) { - mpq_clear(p1); mpq_clear(p2); free(p1); free(p2); - return ObjectTypeSet::dynamicType(); + if (mpq_sgn(p2) == 0) { + mpq_clear(p1); + mpq_clear(p2); + free(p1); + free(p2); + return ObjectTypeSet::dynamicType(); } mpq_t res; mpq_init(res); mpq_div(res, p1, p2); auto ret = mgr.createQ(res); - mpq_clear(p1); mpq_clear(p2); mpq_clear(res); - free(p1); free(p2); + mpq_clear(p1); + mpq_clear(p2); + mpq_clear(res); + free(p1); + free(p2); return ret; } - if (p1) { mpq_clear(p1); free(p1); } - if (p2) { mpq_clear(p2); free(p2); } + if (p1) { + mpq_clear(p1); + free(p1); + } + if (p2) { + mpq_clear(p2); + free(p2); + } return ObjectTypeSet::dynamicType(); }; typeIntrinsics["Integer_div"] = typeIntrinsics["BigInteger_div"]; // Ratio pure math folding auto regQMath = [&](const string &symbol, auto op) { - typeIntrinsics[symbol] = [&mgr, op](const vector &args) -> ObjectTypeSet { - if (args.size() != 2) return ObjectTypeSet(ratioType, false); + typeIntrinsics[symbol] = + [&mgr, op](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) + return ObjectTypeSet(ratioType, false); mpq_ptr p1 = mgr.getQ(args[0]); mpq_ptr p2 = mgr.getQ(args[1]); if (p1 && p2) { @@ -235,12 +309,21 @@ void registerMathIntrinsics(InvokeManager &mgr) { mpq_init(res); op(res, p1, p2); auto ret = mgr.createQ(res); - mpq_clear(p1); mpq_clear(p2); mpq_clear(res); - free(p1); free(p2); + mpq_clear(p1); + mpq_clear(p2); + mpq_clear(res); + free(p1); + free(p2); return ret; } - if (p1) { mpq_clear(p1); free(p1); } - if (p2) { mpq_clear(p2); free(p2); } + if (p1) { + mpq_clear(p1); + free(p1); + } + if (p2) { + mpq_clear(p2); + free(p2); + } return ObjectTypeSet(ratioType, false); }; }; @@ -248,13 +331,16 @@ void registerMathIntrinsics(InvokeManager &mgr) { regQMath("Ratio_sub", mpq_sub); regQMath("Ratio_mul", mpq_mul); regQMath("Ratio_div", [](mpq_t r, mpq_t a, mpq_t b) { - if (mpq_sgn(b) != 0) mpq_div(r, a, b); + if (mpq_sgn(b) != 0) + mpq_div(r, a, b); }); // Mixed type folding helpers auto regMixZ = [&](const string &symbol, auto op) { - typeIntrinsics[symbol] = [&mgr, op](const vector &args) -> ObjectTypeSet { - if (args.size() != 2) return ObjectTypeSet(bigIntegerType, false); + typeIntrinsics[symbol] = + [&mgr, op](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) + return ObjectTypeSet(bigIntegerType, false); mpz_ptr p1 = mgr.getZ(args[0]); mpz_ptr p2 = mgr.getZ(args[1]); if (p1 && p2) { @@ -262,24 +348,44 @@ void registerMathIntrinsics(InvokeManager &mgr) { mpz_init(res); op(res, p1, p2); auto ret = mgr.createZ(res); - mpz_clear(p1); mpz_clear(p2); mpz_clear(res); - free(p1); free(p2); + mpz_clear(p1); + mpz_clear(p2); + mpz_clear(res); + free(p1); + free(p2); return ret; } - if (p1) { mpz_clear(p1); free(p1); } - if (p2) { mpz_clear(p2); free(p2); } + if (p1) { + mpz_clear(p1); + free(p1); + } + if (p2) { + mpz_clear(p2); + free(p2); + } return ObjectTypeSet(bigIntegerType, false); }; }; - regMixZ("Add_IB", mpz_add); regMixZ("Add_BI", mpz_add); - regMixZ("Sub_IB", mpz_sub); regMixZ("Sub_BI", mpz_sub); - regMixZ("Mul_IB", mpz_mul); regMixZ("Mul_BI", mpz_mul); - regMixZ("Div_IB", [](mpz_t r, mpz_t a, mpz_t b) { if (mpz_sgn(b) != 0) mpz_tdiv_q(r, a, b); }); - regMixZ("Div_BI", [](mpz_t r, mpz_t a, mpz_t b) { if (mpz_sgn(b) != 0) mpz_tdiv_q(r, a, b); }); + regMixZ("Add_IB", mpz_add); + regMixZ("Add_BI", mpz_add); + regMixZ("Sub_IB", mpz_sub); + regMixZ("Sub_BI", mpz_sub); + regMixZ("Mul_IB", mpz_mul); + regMixZ("Mul_BI", mpz_mul); + regMixZ("Div_IB", [](mpz_t r, mpz_t a, mpz_t b) { + if (mpz_sgn(b) != 0) + mpz_tdiv_q(r, a, b); + }); + regMixZ("Div_BI", [](mpz_t r, mpz_t a, mpz_t b) { + if (mpz_sgn(b) != 0) + mpz_tdiv_q(r, a, b); + }); auto regMixD = [&](const string &symbol, auto op) { - typeIntrinsics[symbol] = [&mgr, op](const vector &args) -> ObjectTypeSet { - if (args.size() != 2) return ObjectTypeSet(doubleType, false); + typeIntrinsics[symbol] = + [&mgr, op](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) + return ObjectTypeSet(doubleType, false); double d1 = mgr.getD(args[0]); double d2 = mgr.getD(args[1]); return ObjectTypeSet(doubleType, false, new ConstantDouble(op(d1, d2))); @@ -311,8 +417,10 @@ void registerMathIntrinsics(InvokeManager &mgr) { regMixD("Div_DR", [](double a, double b) { return a / b; }); auto regMixQ = [&](const string &symbol, auto op) { - typeIntrinsics[symbol] = [&mgr, op](const vector &args) -> ObjectTypeSet { - if (args.size() != 2) return ObjectTypeSet(ratioType, false); + typeIntrinsics[symbol] = + [&mgr, op](const vector &args) -> ObjectTypeSet { + if (args.size() != 2) + return ObjectTypeSet(ratioType, false); mpq_ptr p1 = mgr.getQ(args[0]); mpq_ptr p2 = mgr.getQ(args[1]); if (p1 && p2) { @@ -320,26 +428,52 @@ void registerMathIntrinsics(InvokeManager &mgr) { mpq_init(res); op(res, p1, p2); auto ret = mgr.createQ(res); - mpq_clear(p1); mpq_clear(p2); mpq_clear(res); - free(p1); free(p2); + mpq_clear(p1); + mpq_clear(p2); + mpq_clear(res); + free(p1); + free(p2); return ret; } - if (p1) { mpq_clear(p1); free(p1); } - if (p2) { mpq_clear(p2); free(p2); } + if (p1) { + mpq_clear(p1); + free(p1); + } + if (p2) { + mpq_clear(p2); + free(p2); + } return ObjectTypeSet(ratioType, false); }; }; - regMixQ("Add_BR", mpq_add); regMixQ("Add_RB", mpq_add); - regMixQ("Sub_BR", mpq_sub); regMixQ("Sub_RB", mpq_sub); - regMixQ("Mul_BR", mpq_mul); regMixQ("Mul_RB", mpq_mul); - regMixQ("Div_BR", [](mpq_t r, mpq_t a, mpq_t b) { if (mpq_sgn(b) != 0) mpq_div(r, a, b); }); - regMixQ("Div_RB", [](mpq_t r, mpq_t a, mpq_t b) { if (mpq_sgn(b) != 0) mpq_div(r, a, b); }); - regMixQ("Add_IR", mpq_add); regMixQ("Add_RI", mpq_add); - regMixQ("Sub_IR", mpq_sub); regMixQ("Sub_RI", mpq_sub); - regMixQ("Mul_IR", mpq_mul); regMixQ("Mul_RI", mpq_mul); - regMixQ("Div_IR", [](mpq_t r, mpq_t a, mpq_t b) { if (mpq_sgn(b) != 0) mpq_div(r, a, b); }); - regMixQ("Div_RI", [](mpq_t r, mpq_t a, mpq_t b) { if (mpq_sgn(b) != 0) mpq_div(r, a, b); }); - + regMixQ("Add_BR", mpq_add); + regMixQ("Add_RB", mpq_add); + regMixQ("Sub_BR", mpq_sub); + regMixQ("Sub_RB", mpq_sub); + regMixQ("Mul_BR", mpq_mul); + regMixQ("Mul_RB", mpq_mul); + regMixQ("Div_BR", [](mpq_t r, mpq_t a, mpq_t b) { + if (mpq_sgn(b) != 0) + mpq_div(r, a, b); + }); + regMixQ("Div_RB", [](mpq_t r, mpq_t a, mpq_t b) { + if (mpq_sgn(b) != 0) + mpq_div(r, a, b); + }); + regMixQ("Add_IR", mpq_add); + regMixQ("Add_RI", mpq_add); + regMixQ("Sub_IR", mpq_sub); + regMixQ("Sub_RI", mpq_sub); + regMixQ("Mul_IR", mpq_mul); + regMixQ("Mul_RI", mpq_mul); + regMixQ("Div_IR", [](mpq_t r, mpq_t a, mpq_t b) { + if (mpq_sgn(b) != 0) + mpq_div(r, a, b); + }); + regMixQ("Div_RI", [](mpq_t r, mpq_t a, mpq_t b) { + if (mpq_sgn(b) != 0) + mpq_div(r, a, b); + }); // --- Codegen --- @@ -347,12 +481,15 @@ void registerMathIntrinsics(InvokeManager &mgr) { return b.CreateFAdd(args[0], args[1]); }; - auto createCheckedOp = [&mgr, &theModule, &types](const std::string &llvmIntrinsic, - const std::string &errorMessage) { - return [&mgr, &theModule, &types, llvmIntrinsic, errorMessage](llvm::IRBuilder<> &b, - std::vector args) { - Function *fn = Intrinsic::getDeclaration(&theModule, - llvmIntrinsic == "sadd" ? Intrinsic::sadd_with_overflow : Intrinsic::ssub_with_overflow, + auto createCheckedOp = [&mgr, &theModule, + &types](const std::string &llvmIntrinsic, + const std::string &errorMessage) { + return [&mgr, &theModule, &types, llvmIntrinsic, errorMessage]( + llvm::IRBuilder<> &b, std::vector args) { + Function *fn = Intrinsic::getOrInsertDeclaration( + &theModule, + llvmIntrinsic == "sadd" ? Intrinsic::sadd_with_overflow + : Intrinsic::ssub_with_overflow, {b.getInt32Ty()}); Value *resStruct = b.CreateCall(fn, args); Value *res = b.CreateExtractValue(resStruct, {0}); @@ -361,16 +498,20 @@ void registerMathIntrinsics(InvokeManager &mgr) { BasicBlock *insertBB = b.GetInsertBlock(); Function *currentFn = insertBB->getParent(); - BasicBlock *overflowBB = BasicBlock::Create(theModule.getContext(), "overflow", currentFn); - BasicBlock *continueBB = BasicBlock::Create(theModule.getContext(), "no_overflow", currentFn); + BasicBlock *overflowBB = + BasicBlock::Create(theModule.getContext(), "overflow", currentFn); + BasicBlock *continueBB = + BasicBlock::Create(theModule.getContext(), "no_overflow", currentFn); MDBuilder MDB(theModule.getContext()); MDNode *Weights = MDB.createBranchWeights(1, 1000000); b.CreateCondBr(overflow, overflowBB, continueBB, Weights); b.SetInsertPoint(overflowBB); - FunctionType *throwFnTy = FunctionType::get(types.voidTy, {mgr.types.ptrTy}, false); - FunctionCallee throwFn = theModule.getOrInsertFunction("throwArithmeticException_C", throwFnTy); + FunctionType *throwFnTy = + FunctionType::get(types.voidTy, {mgr.types.ptrTy}, false); + FunctionCallee throwFn = theModule.getOrInsertFunction( + "throwArithmeticException_C", throwFnTy); Value *msg = b.CreateGlobalString(errorMessage); b.CreateCall(throwFn, {msg}); b.CreateUnreachable(); @@ -399,10 +540,14 @@ void registerMathIntrinsics(InvokeManager &mgr) { }; // BigInt/Ratio Codegen - auto regZCodegen = [&mgr, &intrinsics](const string &symbol, const string &fnName) { + auto regZCodegen = [&mgr, &intrinsics](const string &symbol, + const string &fnName) { intrinsics[symbol] = [&mgr, fnName](auto &b, auto args) { ObjectTypeSet z(bigIntegerType); - return mgr.invokeRuntime(fnName, &z, {z, z}, {TypedValue(z, args[0]), TypedValue(z, args[1])}).value; + return mgr + .invokeRuntime(fnName, &z, {z, z}, + {TypedValue(z, args[0]), TypedValue(z, args[1])}) + .value; }; }; regZCodegen("BigInteger_add", "BigInteger_add"); @@ -411,19 +556,29 @@ void registerMathIntrinsics(InvokeManager &mgr) { intrinsics["BigInteger_div"] = [&mgr](auto &b, auto args) { ObjectTypeSet z(bigIntegerType); ObjectTypeSet anyT = ObjectTypeSet::all(); - return mgr.invokeRuntime("BigInteger_div", &anyT, {z, z}, {TypedValue(z, args[0]), TypedValue(z, args[1])}).value; + return mgr + .invokeRuntime("BigInteger_div", &anyT, {z, z}, + {TypedValue(z, args[0]), TypedValue(z, args[1])}) + .value; }; intrinsics["Integer_div"] = [&mgr](auto &b, auto args) { ObjectTypeSet i(integerType); ObjectTypeSet anyT = ObjectTypeSet::all(); - return mgr.invokeRuntime("Integer_div", &anyT, {i, i}, {TypedValue(i, args[0]), TypedValue(i, args[1])}).value; + return mgr + .invokeRuntime("Integer_div", &anyT, {i, i}, + {TypedValue(i, args[0]), TypedValue(i, args[1])}) + .value; }; - auto regQCodegen = [&mgr, &intrinsics](const string &symbol, const string &fnName) { + auto regQCodegen = [&mgr, &intrinsics](const string &symbol, + const string &fnName) { intrinsics[symbol] = [&mgr, fnName](auto &b, auto args) { ObjectTypeSet q(ratioType); ObjectTypeSet anyT = ObjectTypeSet::all(); - return mgr.invokeRuntime(fnName, &anyT, {q, q}, {TypedValue(q, args[0]), TypedValue(q, args[1])}).value; + return mgr + .invokeRuntime(fnName, &anyT, {q, q}, + {TypedValue(q, args[0]), TypedValue(q, args[1])}) + .value; }; }; regQCodegen("Ratio_add", "Ratio_add"); @@ -471,10 +626,11 @@ void registerMathIntrinsics(InvokeManager &mgr) { TypedValue v1 = mgr.invokeRuntime( "BigInteger_createFromInt", &retType, {ObjectTypeSet(integerType)}, {TypedValue(ObjectTypeSet(integerType), args[0])}); - return mgr.invokeRuntime( - opSymbol, &retType, - {ObjectTypeSet(bigIntegerType), ObjectTypeSet(bigIntegerType)}, - {v1, TypedValue(ObjectTypeSet(bigIntegerType), args[1])}) + return mgr + .invokeRuntime( + opSymbol, &retType, + {ObjectTypeSet(bigIntegerType), ObjectTypeSet(bigIntegerType)}, + {v1, TypedValue(ObjectTypeSet(bigIntegerType), args[1])}) .value; }; }; @@ -485,10 +641,11 @@ void registerMathIntrinsics(InvokeManager &mgr) { TypedValue v2 = mgr.invokeRuntime( "BigInteger_createFromInt", &retType, {ObjectTypeSet(integerType)}, {TypedValue(ObjectTypeSet(integerType), args[1])}); - return mgr.invokeRuntime( - opSymbol, &retType, - {ObjectTypeSet(bigIntegerType), ObjectTypeSet(bigIntegerType)}, - {TypedValue(ObjectTypeSet(bigIntegerType), args[0]), v2}) + return mgr + .invokeRuntime( + opSymbol, &retType, + {ObjectTypeSet(bigIntegerType), ObjectTypeSet(bigIntegerType)}, + {TypedValue(ObjectTypeSet(bigIntegerType), args[0]), v2}) .value; }; }; @@ -505,10 +662,10 @@ void registerMathIntrinsics(InvokeManager &mgr) { auto makeDB = [&mgr, &intrinsics](const std::string &opSymbol) { return [&mgr, &intrinsics, opSymbol](auto &b, auto args) { ObjectTypeSet doubleTypeSet(doubleType); - TypedValue v2 = - mgr.invokeRuntime("BigInteger_toDouble", &doubleTypeSet, - {ObjectTypeSet(bigIntegerType)}, - {TypedValue(ObjectTypeSet(bigIntegerType), args[1])}); + TypedValue v2 = mgr.invokeRuntime( + "BigInteger_toDouble", &doubleTypeSet, + {ObjectTypeSet(bigIntegerType)}, + {TypedValue(ObjectTypeSet(bigIntegerType), args[1])}); return intrinsics[opSymbol](b, {args[0], v2.value}); }; }; @@ -516,10 +673,10 @@ void registerMathIntrinsics(InvokeManager &mgr) { auto makeBD = [&mgr, &intrinsics](const std::string &opSymbol) { return [&mgr, &intrinsics, opSymbol](auto &b, auto args) { ObjectTypeSet doubleTypeSet(doubleType); - TypedValue v1 = - mgr.invokeRuntime("BigInteger_toDouble", &doubleTypeSet, - {ObjectTypeSet(bigIntegerType)}, - {TypedValue(ObjectTypeSet(bigIntegerType), args[0])}); + TypedValue v1 = mgr.invokeRuntime( + "BigInteger_toDouble", &doubleTypeSet, + {ObjectTypeSet(bigIntegerType)}, + {TypedValue(ObjectTypeSet(bigIntegerType), args[0])}); return intrinsics[opSymbol](b, {v1.value, args[1]}); }; }; @@ -566,11 +723,12 @@ void registerMathIntrinsics(InvokeManager &mgr) { ObjectTypeSet retType(ratioType); ObjectTypeSet bigIntTypeSet(bigIntegerType); ObjectTypeSet allTypeSet = ObjectTypeSet::all(); - TypedValue v2 = - mgr.invokeRuntime("Ratio_createFromBigInteger", &retType, {bigIntTypeSet}, - {TypedValue(bigIntTypeSet, args[1])}); - return mgr.invokeRuntime(opSymbol, &allTypeSet, {retType, retType}, - {TypedValue(retType, args[0]), v2}) + TypedValue v2 = mgr.invokeRuntime("Ratio_createFromBigInteger", &retType, + {bigIntTypeSet}, + {TypedValue(bigIntTypeSet, args[1])}); + return mgr + .invokeRuntime(opSymbol, &allTypeSet, {retType, retType}, + {TypedValue(retType, args[0]), v2}) .value; }; }; @@ -580,11 +738,12 @@ void registerMathIntrinsics(InvokeManager &mgr) { ObjectTypeSet retType(ratioType); ObjectTypeSet bigIntTypeSet(bigIntegerType); ObjectTypeSet allTypeSet = ObjectTypeSet::all(); - TypedValue v1 = - mgr.invokeRuntime("Ratio_createFromBigInteger", &retType, {bigIntTypeSet}, - {TypedValue(bigIntTypeSet, args[0])}); - return mgr.invokeRuntime(opSymbol, &allTypeSet, {retType, retType}, - {v1, TypedValue(retType, args[1])}) + TypedValue v1 = mgr.invokeRuntime("Ratio_createFromBigInteger", &retType, + {bigIntTypeSet}, + {TypedValue(bigIntTypeSet, args[0])}); + return mgr + .invokeRuntime(opSymbol, &allTypeSet, {retType, retType}, + {v1, TypedValue(retType, args[1])}) .value; }; }; @@ -604,9 +763,10 @@ void registerMathIntrinsics(InvokeManager &mgr) { ObjectTypeSet allTypeSet = ObjectTypeSet::all(); TypedValue v2 = mgr.invokeRuntime("Ratio_createFromInt", &retType, {intTypeSet}, - {TypedValue(intTypeSet, args[1])}); - return mgr.invokeRuntime(opSymbol, &allTypeSet, {retType, retType}, - {TypedValue(retType, args[0]), v2}) + {TypedValue(intTypeSet, args[1])}); + return mgr + .invokeRuntime(opSymbol, &allTypeSet, {retType, retType}, + {TypedValue(retType, args[0]), v2}) .value; }; }; @@ -618,9 +778,10 @@ void registerMathIntrinsics(InvokeManager &mgr) { ObjectTypeSet allTypeSet = ObjectTypeSet::all(); TypedValue v1 = mgr.invokeRuntime("Ratio_createFromInt", &retType, {intTypeSet}, - {TypedValue(intTypeSet, args[0])}); - return mgr.invokeRuntime(opSymbol, &allTypeSet, {retType, retType}, - {v1, TypedValue(retType, args[1])}) + {TypedValue(intTypeSet, args[0])}); + return mgr + .invokeRuntime(opSymbol, &allTypeSet, {retType, retType}, + {v1, TypedValue(retType, args[1])}) .value; }; }; diff --git a/backend-v2/codegen/ops/InstanceCallNode.cpp b/backend-v2/codegen/ops/InstanceCallNode.cpp new file mode 100644 index 00000000..6fb88e7e --- /dev/null +++ b/backend-v2/codegen/ops/InstanceCallNode.cpp @@ -0,0 +1,69 @@ +#include "../../tools/EdnParser.h" +#include "../../tools/RTValueWrapper.h" +#include "../../types/ConstantBool.h" +#include "../../types/ConstantDouble.h" +#include "../../types/ConstantInteger.h" +#include "../CodeGen.h" +#include "bridge/Exceptions.h" +#include "bytecode.pb.h" +#include "codegen/TypedValue.h" +#include + +using namespace std; +using namespace llvm; +using namespace clojure::rt::protobuf::bytecode; + +namespace rt { + +TypedValue CodeGen::codegen(const Node &node, const InstanceCallNode &subnode, + const ObjectTypeSet &typeRestrictions) { + + return TypedValue(ObjectTypeSet::all(), nullptr); +} + +ObjectTypeSet CodeGen::getType(const Node &node, + const InstanceCallNode &subnode, + const ObjectTypeSet &typeRestrictions) { + auto targetType = getType(subnode.instance(), ObjectTypeSet::all()); + auto methodName = subnode.method(); + uint64_t classId = 0; + + if (targetType.isDetermined()) { + if (targetType.determinedType() != deftypeType) { + classId = targetType.determinedType(); + } else if (targetType.isDeterminedClass()) { + classId = targetType.determinedClass(); + } + } + + if (classId) { // Static dispatch + auto classMethods = TheProgramme->InstanceCallLibrary.find(classId); + if (classMethods == TheProgramme->InstanceCallLibrary.end()) + return ObjectTypeSet::dynamicType(); // class not found + + auto methods = classMethods->second.find(methodName); + if (methods != classMethods->second.end()) { // class and method found + vector types{targetType}; + for (auto arg : subnode.args()) + types.push_back(getType(arg, ObjectTypeSet::all())); + string requiredTypes = ObjectTypeSet::typeStringForArgs(types); + for (auto method : methods->second) { + auto methodTypes = typesForArgString(node, method.first); + if (method.first != requiredTypes) + continue; + return method.second + .first(this, methodName + " " + requiredTypes, node, types) + .restriction(typeRestrictions); + } + return ObjectTypeSet::dynamicType(); // class found, static method found, + // but does not expect arity 0 + } else { // class found, static method not found - dynamic method or not + // present at all + return ObjectTypeSet::dynamicType(); + } + } + + // CONSIDER: Check if dynamic method exists (this tells us nothing about its + // type, but lets us throw an exception) + return ObjectTypeSet::all(); +} diff --git a/backend-v2/codegen/ops/LetNode.cpp b/backend-v2/codegen/ops/LetNode.cpp index 4ba5aecf..0cfd0d18 100644 --- a/backend-v2/codegen/ops/LetNode.cpp +++ b/backend-v2/codegen/ops/LetNode.cpp @@ -21,16 +21,6 @@ TypedValue CodeGen::codegen(const Node &node, const LetNode &subnode, auto name = binding.name(); variableBindingStack.set(name, init); variableTypesBindingsStack.set(name, init.type); - - // Memory guidance for the binding - for (const auto &guidance : bindingNode.dropmemory()) { - memoryManagement.dynamicMemoryGuidance(guidance); - } - } - - // Memory guidance for the Let node itself (before body) - for (const auto &guidance : node.dropmemory()) { - memoryManagement.dynamicMemoryGuidance(guidance); } auto retVal = codegen(subnode.body(), typeRestrictions); diff --git a/backend-v2/jit/JITEngine.cpp b/backend-v2/jit/JITEngine.cpp index edee3dd2..ea58b38f 100644 --- a/backend-v2/jit/JITEngine.cpp +++ b/backend-v2/jit/JITEngine.cpp @@ -1,11 +1,18 @@ #include "JITEngine.h" #include "../RuntimeHeaders.h" #include "../runtime/Numbers.h" +#include "../tools/EdnParser.h" #include "bridge/Exceptions.h" +#include +#include +#include #include +#include #include -#include "../tools/EdnParser.h" +#include +#include +extern "C" void *__emutls_get_address(void *); namespace rt { @@ -17,8 +24,18 @@ JITEngine::JITEngine(ThreadsafeCompilerState &state, size_t numThreads) llvm::InitializeNativeTargetAsmPrinter(); llvm::InitializeNativeTargetAsmParser(); + auto JTMB = llvm::orc::JITTargetMachineBuilder::detectHost(); + if (!JTMB) + throw std::runtime_error("Failed to detect host"); + + auto TMTmp = JTMB->createTargetMachine(); + if (!TMTmp) + throw std::runtime_error("Failed to create target machine"); + TM = std::move(*TMTmp); + auto JITExp = llvm::orc::LLJITBuilder() + .setJITTargetMachineBuilder(*JTMB) .setObjectLinkingLayerCreator( [&](llvm::orc::ExecutionSession &ES, const llvm::Triple &TT) { auto ObjLinkingLayer = @@ -59,13 +76,21 @@ JITEngine::JITEngine(ThreadsafeCompilerState &state, size_t numThreads) DL.getGlobalPrefix()))); registerRuntimeSymbols(); + + // Load runtime bitcode + auto bufferOrErr = llvm::MemoryBuffer::getFile("runtime/runtime_uber.bc"); + if (!bufferOrErr) { + llvm::errs() << "Warning: Could not load runtime/runtime_uber.bc\n"; + } else { + runtimeBitcodeBuffer = std::move(*bufferOrErr); + } } std::future JITEngine::compileAST(const Node &AST, const std::string &moduleName, llvm::OptimizationLevel level, bool printModule) { // Wrap logic in a task for the pool - return pool.enqueue([this, AST, moduleName, + return pool.enqueue([this, AST, moduleName, level, printModule]() -> llvm::orc::ExecutorAddr { auto codeGenerator = CodeGen(moduleName, threadsafeState); @@ -85,22 +110,15 @@ JITEngine::compileAST(const Node &AST, const std::string &moduleName, auto module = std::move(result.module); auto constants = std::move(result.constants); - // 4. Optimize WITHOUT LOCK - // if (level != llvm::OptimizationLevel::O0) { - // this->optimize(*mod, Level); - // } + this->optimize(*module, level, fName); - // --- TUTAJ WYPISUJEMY IR --- - if (module && printModule) { - llvm::outs() << "\n=== Generated LLVM IR for: '" << moduleName + if (printModule && module) { + llvm::outs() << "\n=== Optimized LLVM IR for: '" << moduleName << "' ===\n"; - module->print(llvm::outs(), - nullptr); // Wypisuje IR do standardowego wyjścia LLVM - llvm::outs() << "=============================================\n"; - llvm::outs().flush(); // Wymuszamy opróżnienie bufora, żeby tekst nie - // zniknął przy błędzie + module->print(llvm::outs(), nullptr); + llvm::outs() << "===============================================\n"; + llvm::outs().flush(); } - // --------------------------- llvm::orc::ThreadSafeModule TSM(std::move(module), *context); @@ -198,7 +216,8 @@ void JITEngine::registerRuntimeSymbols() { runtimeSymbols.insert(absoluteSymbol("Numbers_sub", (void *)Numbers_sub)); runtimeSymbols.insert(absoluteSymbol("Numbers_mul", (void *)Numbers_mul)); runtimeSymbols.insert(absoluteSymbol("Numbers_div", (void *)Numbers_div)); - runtimeSymbols.insert(absoluteSymbol("Numbers_toDouble", (void *)Numbers_toDouble)); + runtimeSymbols.insert( + absoluteSymbol("Numbers_toDouble", (void *)Numbers_toDouble)); runtimeSymbols.insert(absoluteSymbol("Numbers_lt", (void *)Numbers_lt)); runtimeSymbols.insert(absoluteSymbol("Numbers_lte", (void *)Numbers_lte)); runtimeSymbols.insert(absoluteSymbol("Numbers_gt", (void *)Numbers_gt)); @@ -274,16 +293,26 @@ void JITEngine::registerRuntimeSymbols() { (void *)throwIndexOutOfBoundsException_C)); // ClassExtension / Static Fields - runtimeSymbols.insert(absoluteSymbol("ClassExtension_getIndexedStaticField", - (void *)ClassExtension_getIndexedStaticField)); - runtimeSymbols.insert(absoluteSymbol("ClassExtension_setIndexedStaticField", - (void *)ClassExtension_setIndexedStaticField)); + runtimeSymbols.insert( + absoluteSymbol("ClassExtension_getIndexedStaticField", + (void *)ClassExtension_getIndexedStaticField)); + runtimeSymbols.insert( + absoluteSymbol("ClassExtension_setIndexedStaticField", + (void *)ClassExtension_setIndexedStaticField)); runtimeSymbols.insert(absoluteSymbol("ClassExtension_fieldIndex", (void *)ClassExtension_fieldIndex)); - runtimeSymbols.insert(absoluteSymbol("ClassExtension_staticFieldIndex", - (void *)ClassExtension_staticFieldIndex)); - runtimeSymbols.insert(absoluteSymbol("ClassExtension_resolveInstanceCall", - (void *)ClassExtension_resolveInstanceCall)); + runtimeSymbols.insert( + absoluteSymbol("ClassExtension_staticFieldIndex", + (void *)ClassExtension_staticFieldIndex)); + runtimeSymbols.insert( + absoluteSymbol("ClassExtension_resolveInstanceCall", + (void *)ClassExtension_resolveInstanceCall)); + + // Bridge for emulated TLS required by JIT-compiled code + runtimeSymbols.insert( + absoluteSymbol("___emutls_get_address", (void *)__emutls_get_address)); + runtimeSymbols.insert( + absoluteSymbol("__emutls_get_address", (void *)__emutls_get_address)); cantFail(jit->getMainJITDylib().define( absoluteSymbols(std::move(runtimeSymbols)))); @@ -302,4 +331,127 @@ JITEngine::~JITEngine() { } } +void JITEngine::optimize(llvm::Module &M, llvm::OptimizationLevel Level, + const std::string &entryPoint) { + // No changes here, we are moving this block later. + + if (runtimeBitcodeBuffer) { + auto runtimeModuleOrErr = + llvm::parseBitcodeFile(*runtimeBitcodeBuffer, M.getContext()); + if (!runtimeModuleOrErr) { + llvm::errs() << "Failed to parse runtime bitcode: " + << llvm::toString(runtimeModuleOrErr.takeError()) << "\n"; + } else { + auto &runtimeModule = *runtimeModuleOrErr; + + // We want to link only what's needed and keep linked functions internal + // so they can be inlined and then deleted if not needed elsewere. + if (llvm::Linker::linkModules(M, std::move(runtimeModule), + llvm::Linker::LinkOnlyNeeded)) { + llvm::errs() << "Failed to link runtime bitcode\n"; + } else { + // Shared global variables should not be duplicated (e.g., objectCount, + // Nil_VALUE). We force them to be external declarations so they bind to + // the host process. + for (auto &G : M.globals()) { + if (!G.isDeclaration() && G.hasExternalLinkage()) { + G.setInitializer(nullptr); + G.setLinkage(llvm::GlobalValue::ExternalLinkage); + } + } + + // Mark all functions from runtime as internal so they can be optimized + // away if inlined. We keep the entry point and 'main' external so the + // JIT can find/run them. + for (auto &F : M) { + if (!F.isDeclaration() && F.getName() != "main" && + F.getName() != entryPoint) { + if (F.hasExternalLinkage()) { + F.setLinkage(llvm::GlobalValue::InternalLinkage); + } + } + } + } + } + } + + // 1. Sync target attributes and strip blockers across the module. + // We do this AFTER linking so that functions pulled from runtime bitcode + // are also processed. This is critical for inlining. + std::string bestCPU = TM->getTargetCPU().str(); + std::string bestFeatures = TM->getTargetFeatureString().str(); + + // First pass: Discover "richest" attributes from the merged module. + // Runtime bitcode usually has more detailed feature strings than the TM + // default. + for (auto &F : M) { + if (F.isDeclaration()) + continue; + if (F.hasFnAttribute("target-cpu")) { + auto attr = F.getFnAttribute("target-cpu").getValueAsString(); + if (attr.size() > bestCPU.size()) + bestCPU = attr.str(); + } + if (F.hasFnAttribute("target-features")) { + auto attr = F.getFnAttribute("target-features").getValueAsString(); + if (attr.size() > bestFeatures.size()) + bestFeatures = attr.str(); + } + } + + // Second pass: Apply "gold standard" attributes to EVERY function. + for (auto &F : M) { + if (!F.isDeclaration()) { + if (!bestCPU.empty()) + F.addFnAttr("target-cpu", bestCPU); + if (!bestFeatures.empty()) + F.addFnAttr("target-features", bestFeatures); + + // Force consistent frame-pointer + F.addFnAttr("frame-pointer", "non-leaf"); + + F.removeFnAttr(llvm::Attribute::OptimizeNone); + F.removeFnAttr(llvm::Attribute::NoInline); + + // Also remove ssp (stack protector) to avoid potential mismatches, + // and ensure we don't have "no-skipped-passes" which can interfere. + F.removeFnAttr(llvm::Attribute::StackProtect); + F.removeFnAttr(llvm::Attribute::StackProtectReq); + F.removeFnAttr(llvm::Attribute::StackProtectStrong); + F.removeFnAttr("no-skipped-passes"); + } + } + + llvm::LoopAnalysisManager LAM; + llvm::FunctionAnalysisManager FAM; + llvm::CGSCCAnalysisManager CGAM; + llvm::ModuleAnalysisManager MAM; + + llvm::PassBuilder PB(TM->getTargetTriple().isOSDarwin() ? nullptr : TM.get()); + + PB.registerModuleAnalyses(MAM); + PB.registerCGSCCAnalyses(CGAM); + PB.registerFunctionAnalyses(FAM); + PB.registerLoopAnalyses(LAM); + PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); + + // Add target-specific analysis + FAM.registerPass([&] { return TM->getTargetIRAnalysis(); }); + + llvm::ModulePassManager MPM; + + if (Level == llvm::OptimizationLevel::O3) { + // For O3 we want VERY aggressive inlining to pull in linked runtime + // functions. + llvm::InlineParams IP; + IP = llvm::getInlineParams(3, 0); + IP.DefaultThreshold = 1500; // Aggressive + IP.HintThreshold = 2500; + MPM.addPass(llvm::ModuleInlinerWrapperPass(IP)); + } + + MPM.addPass(PB.buildPerModuleDefaultPipeline(Level)); + MPM.run(M, MAM); +} + } // namespace rt diff --git a/backend-v2/jit/JITEngine.h b/backend-v2/jit/JITEngine.h index 6b0045a8..e6111272 100644 --- a/backend-v2/jit/JITEngine.h +++ b/backend-v2/jit/JITEngine.h @@ -22,6 +22,7 @@ #include "llvm/Support/Error.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Target/TargetMachine.h" // Standard Library #include "../codegen/CodeGen.h" @@ -39,12 +40,16 @@ namespace rt { class JITEngine { std::unique_ptr jit; + std::unique_ptr TM; ThreadPool pool; std::mutex engineMutex; std::map functionTrackers; std::map> moduleConstants; std::map> capturedObjectBuffers; ThreadsafeCompilerState &threadsafeState; + + std::unique_ptr runtimeBitcodeBuffer; + void optimize(llvm::Module &M, llvm::OptimizationLevel Level, const std::string &entryPoint); void registerRuntimeSymbols(); public: diff --git a/backend-v2/main.cpp b/backend-v2/main.cpp index d3fcc114..91f53fae 100644 --- a/backend-v2/main.cpp +++ b/backend-v2/main.cpp @@ -33,12 +33,24 @@ using namespace llvm; #include "RuntimeHeaders.h" +extern "C" void *__emutls_get_address(void *); + int main(int argc, char *argv[]) { + // Force the linker to include ___emutls_get_address from the Clang runtime. + // This is required on macOS when JIT-compiled code uses thread-local storage, + // as the JIT session needs to resolve this symbol. + volatile void *force_emutls = (void *)&__emutls_get_address; + (void)force_emutls; + setbuf(stdout, NULL); if (argc != 2) { - cout << "Please specify the filename for compilation" << endl; + cout << "Usage: clojure-rt " << endl; return -1; } + + std::string filename = argv[1]; + llvm::OptimizationLevel optLevel = llvm::OptimizationLevel::O3; + int retVal = -1; GOOGLE_PROTOBUF_VERIFY_VERSION; @@ -65,7 +77,7 @@ int main(int argc, char *argv[]) { cout << "Loading root..." << endl; clojure::rt::protobuf::bytecode::Programme astRoot; { - fstream input(argv[1], ios::in | ios::binary); + fstream input(filename, ios::in | ios::binary); if (!astRoot.ParseFromIstream(&input)) { cerr << "Failed to parse bytecode." << endl; return -1; @@ -88,11 +100,10 @@ int main(int argc, char *argv[]) { state.storeInternalProtocols(interfaces); cout << "Compiling classes..." << endl; - auto compiled = engine - .compileAST(astClasses.nodes(0), "__classes", - llvm::OptimizationLevel::O0, false) - .get() - .toPtr(); + auto compiled = + engine.compileAST(astClasses.nodes(0), "__classes", optLevel, false) + .get() + .toPtr(); cout << "Calling classes..." << endl; RTValue classes = compiled(); @@ -105,8 +116,7 @@ int main(int argc, char *argv[]) { cout << "=============================" << endl; cout << "Compiling!!!" << endl; std::string moduleName = "__repl__" + std::to_string(j); - auto f = engine.compileAST(topLevelNode, moduleName, - llvm::OptimizationLevel::O0, true); + auto f = engine.compileAST(topLevelNode, moduleName, optLevel, true); RTValue whaat = f.get().toPtr()(); String *s = toString(whaat); diff --git a/backend-v2/runtime/CMakeLists.txt b/backend-v2/runtime/CMakeLists.txt index 17813bf1..088608a2 100644 --- a/backend-v2/runtime/CMakeLists.txt +++ b/backend-v2/runtime/CMakeLists.txt @@ -47,6 +47,61 @@ find_package_handle_standard_args(GMP DEFAULT_MSG GMP_INCLUDE_DIRS GMP_LIBRARY) mark_as_advanced(GMP_INCLUDE_DIRS GMP_LIBRARY) include_directories(${GMP_INCLUDE_DIRS}) +######################################################################## +# BITCODE GENERATION FOR JIT INTROSPECTION +# ############################################################################## + +if(NOT LLVM_TOOLS_BINARY_DIR AND LLVM_DIR) + # If LLVM_DIR is /prefix/lib/cmake/llvm, then tools are in /prefix/bin + get_filename_component(LLVM_PREFIX "${LLVM_DIR}/../../.." ABSOLUTE) + set(LLVM_TOOLS_BINARY_DIR "${LLVM_PREFIX}/bin") +endif() + +# 1. Find the required LLVM tools +# We use PATHS to ensure we use the tools from the SAME LLVM version as the JIT +find_program(CLANG_EXECUTABLE clang + PATHS ${LLVM_TOOLS_BINARY_DIR} + NO_DEFAULT_PATH + REQUIRED) +find_program(LLVM_LINK_EXECUTABLE llvm-link + PATHS ${LLVM_TOOLS_BINARY_DIR} + NO_DEFAULT_PATH + REQUIRED) + +set(BITCODE_OUTPUTS "") + +# 2. Compile each source file to an individual .bc file +foreach(src ${SOURCES}) + get_filename_component(basename ${src} NAME_WE) + set(bc_output "${CMAKE_CURRENT_BINARY_DIR}/${basename}.bc") + + add_custom_command( + OUTPUT ${bc_output} + COMMAND ${CLANG_EXECUTABLE} -emit-llvm -c + ${CMAKE_CURRENT_SOURCE_DIR}/${src} + -iquote ${CMAKE_CURRENT_SOURCE_DIR} -I${GMP_INCLUDE_DIRS} + -O3 -fPIC + -o ${bc_output} + DEPENDS ${src} + COMMENT "Generating LLVM Bitcode for ${src}" + ) + list(APPEND BITCODE_OUTPUTS ${bc_output}) +endforeach() + +# 3. Link all bitcode files into one uberIR +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/runtime_uber.bc + COMMAND ${LLVM_LINK_EXECUTABLE} ${BITCODE_OUTPUTS} -o ${CMAKE_CURRENT_BINARY_DIR}/runtime_uber.bc + DEPENDS ${BITCODE_OUTPUTS} + COMMENT "Linking all bitcode into runtime_uber.bc" +) + +# 4. Create a target to trigger the build +add_custom_target(runtime_ir ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/runtime_uber.bc) + +######################################################################## + + enable_testing() find_package(Cmocka REQUIRED) diff --git a/backend-v2/runtime/Numbers.c b/backend-v2/runtime/Numbers.c index 9cd30eb6..6043cea6 100644 --- a/backend-v2/runtime/Numbers.c +++ b/backend-v2/runtime/Numbers.c @@ -1,142 +1,159 @@ #include "Numbers.h" -#include "Object.h" -#include "Exceptions.h" #include "BigInteger.h" -#include "Ratio.h" #include "Double.h" +#include "Exceptions.h" #include "Integer.h" +#include "Object.h" +#include "Ratio.h" #include +#include static RTValue narrowBigInt(BigInteger *b) { - if (mpz_fits_sint_p(b->value)) { - int32_t val = (int32_t)mpz_get_si(b->value); - Ptr_release(b); - return RT_boxInt32(val); - } - return RT_boxPtr(b); + if (mpz_fits_sint_p(b->value)) { + int32_t val = (int32_t)mpz_get_si(b->value); + Ptr_release(b); + return RT_boxInt32(val); + } + return RT_boxPtr(b); } static RTValue simplifyResult(RTValue v) { - if (getType(v) == bigIntegerType) { - return narrowBigInt((BigInteger*)RT_unboxPtr(v)); - } - return v; + if (getType(v) == bigIntegerType) { + return narrowBigInt((BigInteger *)RT_unboxPtr(v)); + } + return v; } -static Ratio* toRatio(RTValue v) { - objectType t = getType(v); - if (t == ratioType) return (Ratio*)RT_unboxPtr(v); - if (t == integerType) return Ratio_createFromInt(RT_unboxInt32(v)); - if (t == bigIntegerType) return Ratio_createFromBigInteger((BigInteger*)RT_unboxPtr(v)); - // Double to Ratio is not typically a default implicit promotion in Clojure - return NULL; +static Ratio *toRatio(RTValue v) { + objectType t = getType(v); + if (t == ratioType) + return (Ratio *)RT_unboxPtr(v); + if (t == integerType) + return Ratio_createFromInt(RT_unboxInt32(v)); + if (t == bigIntegerType) + return Ratio_createFromBigInteger((BigInteger *)RT_unboxPtr(v)); + // Double to Ratio is not typically a default implicit promotion in Clojure + return NULL; } -static BigInteger* toBigInt(RTValue v) { - objectType t = getType(v); - if (t == bigIntegerType) return (BigInteger*)RT_unboxPtr(v); - if (t == integerType) return BigInteger_createFromInt(RT_unboxInt32(v)); - return NULL; +static BigInteger *toBigInt(RTValue v) { + objectType t = getType(v); + if (t == bigIntegerType) + return (BigInteger *)RT_unboxPtr(v); + if (t == integerType) + return BigInteger_createFromInt(RT_unboxInt32(v)); + return NULL; } double Numbers_toDouble(RTValue v) { - objectType t = getType(v); - if (t == doubleType) return RT_unboxDouble(v); - if (t == integerType) return (double)RT_unboxInt32(v); - if (t == bigIntegerType) return BigInteger_toDouble((BigInteger*)RT_unboxPtr(v)); - if (t == ratioType) return Ratio_toDouble((Ratio*)RT_unboxPtr(v)); - - // Non-numeric type - release(v); - throwIllegalArgumentException_C("Operation requires a numeric argument"); - return 0.0; + objectType t = getType(v); + if (t == doubleType) + return RT_unboxDouble(v); + if (t == integerType) + return (double)RT_unboxInt32(v); + if (t == bigIntegerType) + return BigInteger_toDouble((BigInteger *)RT_unboxPtr(v)); + if (t == ratioType) + return Ratio_toDouble((Ratio *)RT_unboxPtr(v)); + + // Non-numeric type + release(v); + throwIllegalArgumentException_C("Operation requires a numeric argument"); + return 0.0; } static bool isNumeric(objectType t) { - return t == integerType || t == doubleType || t == bigIntegerType || t == ratioType; + return t == integerType || t == doubleType || t == bigIntegerType || + t == ratioType; } -#define GENERIC_MATH_OP(NAME, OP_INT, OP_BIGINT, OP_RATIO, OP_DOUBLE) \ -RTValue Numbers_##NAME(RTValue a, RTValue b) { \ - objectType ta = getType(a); \ - objectType tb = getType(b); \ - if (!isNumeric(ta) || !isNumeric(tb)) { \ - release(a); release(b); \ - throwIllegalArgumentException_C("Arithmetic operation requires numeric arguments"); \ - } \ - if (ta == doubleType || tb == doubleType) { \ - double va = Numbers_toDouble(a); \ - double vb = Numbers_toDouble(b); \ - return RT_boxDouble(va OP_DOUBLE vb); \ - } \ - if (ta == ratioType || tb == ratioType) { \ - Ratio *ra = toRatio(a); \ - Ratio *rb = toRatio(b); \ - return Ratio_##NAME(ra, rb); \ - } \ - if (ta == bigIntegerType || tb == bigIntegerType) { \ - BigInteger *ba = toBigInt(a); \ - BigInteger *bb = toBigInt(b); \ - return simplifyResult(RT_boxPtr(BigInteger_##NAME(ba, bb))); \ - } \ - int32_t va = RT_unboxInt32(a); \ - int32_t vb = RT_unboxInt32(b); \ - int32_t res; \ - if (__builtin_##OP_INT##_overflow(va, vb, &res)) { \ - BigInteger *ba = BigInteger_createFromInt(va); \ - BigInteger *bb = BigInteger_createFromInt(vb); \ - return simplifyResult(RT_boxPtr(BigInteger_##NAME(ba, bb))); \ - } \ - return RT_boxInt32(res); \ -} +#define GENERIC_MATH_OP(NAME, OP_INT, OP_BIGINT, OP_RATIO, OP_DOUBLE) \ + RTValue Numbers_##NAME(RTValue a, RTValue b) { \ + objectType ta = getType(a); \ + objectType tb = getType(b); \ + if (!isNumeric(ta) || !isNumeric(tb)) { \ + release(a); \ + release(b); \ + throwIllegalArgumentException_C( \ + "Arithmetic operation requires numeric arguments"); \ + } \ + if (ta == doubleType || tb == doubleType) { \ + double va = Numbers_toDouble(a); \ + double vb = Numbers_toDouble(b); \ + return RT_boxDouble(va OP_DOUBLE vb); \ + } \ + if (ta == ratioType || tb == ratioType) { \ + Ratio *ra = toRatio(a); \ + Ratio *rb = toRatio(b); \ + return Ratio_##NAME(ra, rb); \ + } \ + if (ta == bigIntegerType || tb == bigIntegerType) { \ + BigInteger *ba = toBigInt(a); \ + BigInteger *bb = toBigInt(b); \ + return simplifyResult(RT_boxPtr(BigInteger_##NAME(ba, bb))); \ + } \ + int32_t va = RT_unboxInt32(a); \ + int32_t vb = RT_unboxInt32(b); \ + int32_t res; \ + if (__builtin_##OP_INT##_overflow(va, vb, &res)) { \ + BigInteger *ba = BigInteger_createFromInt(va); \ + BigInteger *bb = BigInteger_createFromInt(vb); \ + return simplifyResult(RT_boxPtr(BigInteger_##NAME(ba, bb))); \ + } \ + return RT_boxInt32(res); \ + } GENERIC_MATH_OP(add, add, add, add, +) GENERIC_MATH_OP(sub, sub, sub, sub, -) GENERIC_MATH_OP(mul, mul, mul, mul, *) RTValue Numbers_div(RTValue a, RTValue b) { - objectType ta = getType(a); - objectType tb = getType(b); - if (!isNumeric(ta) || !isNumeric(tb)) { - release(a); release(b); - throwIllegalArgumentException_C("Arithmetic operation requires numeric arguments"); - } - if (ta == doubleType || tb == doubleType) { - double res = Numbers_toDouble(a) / Numbers_toDouble(b); - return RT_boxDouble(res); - } - // For division, we often go to Ratio even for Integers (if not divisible) - Ratio *ra = toRatio(a); - Ratio *rb = toRatio(b); - return simplifyResult(Ratio_div(ra, rb)); + objectType ta = getType(a); + objectType tb = getType(b); + if (!isNumeric(ta) || !isNumeric(tb)) { + release(a); + release(b); + throwIllegalArgumentException_C( + "Arithmetic operation requires numeric arguments"); + } + if (ta == doubleType || tb == doubleType) { + double res = Numbers_toDouble(a) / Numbers_toDouble(b); + return RT_boxDouble(res); + } + // For division, we often go to Ratio even for Integers (if not divisible) + Ratio *ra = toRatio(a); + Ratio *rb = toRatio(b); + return simplifyResult(Ratio_div(ra, rb)); } -#define GENERIC_CMP_OP(NAME, OP_BIGINT, OP_RATIO, OP_DOUBLE) \ -bool Numbers_##NAME(RTValue a, RTValue b) { \ - objectType ta = getType(a); \ - objectType tb = getType(b); \ - if (!isNumeric(ta) || !isNumeric(tb)) { \ - release(a); release(b); \ - throwIllegalArgumentException_C("Comparison requires numeric arguments"); \ - } \ - if (ta == doubleType || tb == doubleType) { \ - bool res = Numbers_toDouble(a) OP_DOUBLE Numbers_toDouble(b); \ - return res; \ - } \ - if (ta == ratioType || tb == ratioType) { \ - Ratio *ra = toRatio(a); \ - Ratio *rb = toRatio(b); \ - return Ratio_##NAME(ra, rb); \ - } \ - if (ta == bigIntegerType || tb == bigIntegerType) { \ - BigInteger *ba = toBigInt(a); \ - BigInteger *bb = toBigInt(b); \ - return BigInteger_##NAME(ba, bb); \ - } \ - int32_t va = RT_unboxInt32(a); \ - int32_t vb = RT_unboxInt32(b); \ - return va OP_DOUBLE vb; \ -} +#define GENERIC_CMP_OP(NAME, OP_BIGINT, OP_RATIO, OP_DOUBLE) \ + bool Numbers_##NAME(RTValue a, RTValue b) { \ + objectType ta = getType(a); \ + objectType tb = getType(b); \ + if (!isNumeric(ta) || !isNumeric(tb)) { \ + release(a); \ + release(b); \ + throwIllegalArgumentException_C( \ + "Comparison requires numeric arguments"); \ + } \ + if (ta == doubleType || tb == doubleType) { \ + bool res = Numbers_toDouble(a) OP_DOUBLE Numbers_toDouble(b); \ + return res; \ + } \ + if (ta == ratioType || tb == ratioType) { \ + Ratio *ra = toRatio(a); \ + Ratio *rb = toRatio(b); \ + return Ratio_##NAME(ra, rb); \ + } \ + if (ta == bigIntegerType || tb == bigIntegerType) { \ + BigInteger *ba = toBigInt(a); \ + BigInteger *bb = toBigInt(b); \ + return BigInteger_##NAME(ba, bb); \ + } \ + int32_t va = RT_unboxInt32(a); \ + int32_t vb = RT_unboxInt32(b); \ + return va OP_DOUBLE vb; \ + } GENERIC_CMP_OP(lt, lt, lt, <) GENERIC_CMP_OP(lte, lte, lte, <=) @@ -144,27 +161,82 @@ GENERIC_CMP_OP(gt, gt, gt, >) GENERIC_CMP_OP(gte, gte, gte, >=) bool Numbers_equiv(RTValue a, RTValue b) { - objectType ta = getType(a); - objectType tb = getType(b); - if (ta == tb) { - if (ta == integerType) return RT_unboxInt32(a) == RT_unboxInt32(b); - if (ta == doubleType) return RT_unboxDouble(a) == RT_unboxDouble(b); - // equals() for pointers handles refcounts - bool res = equals(a, b); - release(a); release(b); - return res; - } - // Different types: use a comparison that handles cross-type equivalence - // For now, let's use the same logic as comparisons but specifically for equality - if (ta == doubleType || tb == doubleType) { - bool res = Numbers_toDouble(a) == Numbers_toDouble(b); - return res; - } - // Ratio vs Int/BigInt etc. - Ratio *ra = toRatio(a); - Ratio *rb = toRatio(b); - bool res = Ratio_equals(ra, rb); - Ptr_release(ra); - Ptr_release(rb); + objectType ta = getType(a); + objectType tb = getType(b); + if (ta == tb) { + if (ta == integerType) + return RT_unboxInt32(a) == RT_unboxInt32(b); + if (ta == doubleType) + return RT_unboxDouble(a) == RT_unboxDouble(b); + // equals() for pointers handles refcounts + bool res = equals(a, b); + release(a); + release(b); return res; + } + // Different types: use a comparison that handles cross-type equivalence + // For now, let's use the same logic as comparisons but specifically for + // equality + if (ta == doubleType || tb == doubleType) { + bool res = Numbers_toDouble(a) == Numbers_toDouble(b); + return res; + } + // Ratio vs Int/BigInt etc. + Ratio *ra = toRatio(a); + Ratio *rb = toRatio(b); + bool res = Ratio_equals(ra, rb); + Ptr_release(ra); + Ptr_release(rb); + return res; +} +#include + +#define UNARY_MATH_WRAPPER(NAME, FUNC) \ + RTValue Numbers_##NAME(RTValue a) { \ + double va = Numbers_toDouble(a); \ + return RT_boxDouble(FUNC(va)); \ + } + +#define BINARY_MATH_WRAPPER(NAME, FUNC) \ + RTValue Numbers_##NAME(RTValue a, RTValue b) { \ + if (!isNumeric(getType(a)) || !isNumeric(getType(b))) { \ + release(a); \ + release(b); \ + throwIllegalArgumentException_C( \ + "Math operation requires numeric arguments"); \ + } \ + double va = Numbers_toDouble(a); \ + double vb = Numbers_toDouble(b); \ + return RT_boxDouble(FUNC(va, vb)); \ + } + +UNARY_MATH_WRAPPER(sin, sin) +UNARY_MATH_WRAPPER(cos, cos) +UNARY_MATH_WRAPPER(tan, tan) +UNARY_MATH_WRAPPER(asin, asin) +UNARY_MATH_WRAPPER(acos, acos) +UNARY_MATH_WRAPPER(atan, atan) +UNARY_MATH_WRAPPER(exp, exp) +UNARY_MATH_WRAPPER(exp2, exp2) +RTValue Numbers_exp10(RTValue a) { + double va = Numbers_toDouble(a); + return RT_boxDouble(pow(10.0, va)); +} +UNARY_MATH_WRAPPER(log, log) +UNARY_MATH_WRAPPER(logb, logb) +UNARY_MATH_WRAPPER(log2, log2) +UNARY_MATH_WRAPPER(sqrt, sqrt) +UNARY_MATH_WRAPPER(cbrt, cbrt) +UNARY_MATH_WRAPPER(exp1m, expm1) +UNARY_MATH_WRAPPER(log1p, log1p) +UNARY_MATH_WRAPPER(abs, fabs) + +BINARY_MATH_WRAPPER(atan2, atan2) +BINARY_MATH_WRAPPER(pow, pow) +BINARY_MATH_WRAPPER(hypot, hypot) + +RTValue Numbers_log10(RTValue a) { +#define M_LN10_X 2.30258509299404568402 + double va = Numbers_toDouble(a); + return RT_boxDouble(exp(va * M_LN10_X)); } diff --git a/backend-v2/runtime/Numbers.h b/backend-v2/runtime/Numbers.h index 19cdef42..4aed3176 100644 --- a/backend-v2/runtime/Numbers.h +++ b/backend-v2/runtime/Numbers.h @@ -13,6 +13,27 @@ RTValue Numbers_sub(RTValue a, RTValue b); RTValue Numbers_mul(RTValue a, RTValue b); RTValue Numbers_div(RTValue a, RTValue b); double Numbers_toDouble(RTValue v); +RTValue Numbers_sin(RTValue a); +RTValue Numbers_cos(RTValue a); +RTValue Numbers_tan(RTValue a); +RTValue Numbers_asin(RTValue a); +RTValue Numbers_acos(RTValue a); +RTValue Numbers_atan(RTValue a); +RTValue Numbers_exp(RTValue a); +RTValue Numbers_exp2(RTValue a); +RTValue Numbers_exp10(RTValue a); +RTValue Numbers_log(RTValue a); +RTValue Numbers_log10(RTValue a); +RTValue Numbers_logb(RTValue a); +RTValue Numbers_log2(RTValue a); +RTValue Numbers_sqrt(RTValue a); +RTValue Numbers_cbrt(RTValue a); +RTValue Numbers_exp1m(RTValue a); +RTValue Numbers_log1p(RTValue a); +RTValue Numbers_abs(RTValue a); +RTValue Numbers_atan2(RTValue a, RTValue b); +RTValue Numbers_pow(RTValue a, RTValue b); +RTValue Numbers_hypot(RTValue a, RTValue b); bool Numbers_lt(RTValue a, RTValue b); bool Numbers_lte(RTValue a, RTValue b); diff --git a/backend-v2/runtime/Object.c b/backend-v2/runtime/Object.c index dfdaa4c6..df0298c8 100644 --- a/backend-v2/runtime/Object.c +++ b/backend-v2/runtime/Object.c @@ -47,13 +47,13 @@ void initialise_memory() { extern void *allocate(size_t size); extern void deallocate(void *restrict ptr); -extern void retain(RTValue self); -extern bool release(RTValue self); +void retain(RTValue self); +bool release(RTValue self); extern void autorelease(RTValue self); extern bool release_internal(void *restrict self, bool deallocatesChildren); -extern bool equals(RTValue self, RTValue other); +bool equals(RTValue self, RTValue other); extern uword_t hash(RTValue self); -extern String *toString(RTValue self); +String *toString(RTValue self); extern void Object_create(Object *restrict self, objectType type); extern void Object_retain(Object *restrict self); @@ -66,18 +66,18 @@ extern bool Object_equals(Object *restrict self, Object *restrict other); extern uword_t Object_hash(Object *restrict self); extern String *Object_toString(Object *restrict self); extern bool Object_isReusable(Object *restrict self); -extern bool isReusable(RTValue self); -extern objectType getType(RTValue obj); +bool isReusable(RTValue self); +objectType getType(RTValue obj); extern uword_t combineHash(uword_t lhs, uword_t rhs); extern void Ptr_autorelease(void *self); -extern void Ptr_retain(void *self); -extern bool Ptr_release(void *self); +void Ptr_retain(void *self); +bool Ptr_release(void *self); extern uword_t Ptr_hash(void *self); extern bool Ptr_equals(void *self, void *other); extern bool Ptr_isReusable(void *self); -extern bool equals_managed(RTValue self, RTValue other); +bool equals_managed(RTValue self, RTValue other); 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 b96119ea..d584bf5e 100644 --- a/backend-v2/runtime/Object.h +++ b/backend-v2/runtime/Object.h @@ -66,31 +66,31 @@ extern _Thread_local void *memoryBank[8]; extern _Thread_local int memoryBankSize[8]; void initialise_memory(); -static inline void retain(RTValue self); -static inline bool release(RTValue self); -static inline void promoteToShared(RTValue self); -static inline uword_t hash(RTValue v); -static inline bool equals(RTValue v1, RTValue v2); -static inline bool equals_managed(RTValue v1, RTValue v2); -static inline objectType getType(RTValue v); -static inline String* toString(RTValue v); -static inline void Object_retain(Object* self); -static inline bool Object_release(Object* self); -static inline void Object_promoteToShared(Object* self); -static inline void Object_create(Object* self, objectType type); -static inline uword_t Object_hash(Object* self); -static inline bool Object_equals(Object* self, Object* other); -static inline bool Object_isReusable(Object* self); -static inline bool isReusable(RTValue v); -static inline void Object_promoteToSharedShallow(Object* self, uword_t current); -static inline uword_t Object_getRawRefCount(Object* self); -static inline bool Object_release_internal(Object* self, bool deallocateChildren); -static inline void Ptr_retain(void* ptr); -static inline bool Ptr_release(void* ptr); -static inline void Ptr_autorelease(void* ptr); -static inline uword_t Ptr_hash(void* ptr); -static inline bool Ptr_equals(void* ptr, void* other); -static inline bool Ptr_isReusable(void* ptr); +inline void retain(RTValue self); +inline bool release(RTValue self); +inline void promoteToShared(RTValue self); +inline uword_t hash(RTValue v); +inline bool equals(RTValue v1, RTValue v2); +inline bool equals_managed(RTValue v1, RTValue v2); +inline objectType getType(RTValue v); +inline String* toString(RTValue v); +inline void Object_retain(Object* self); +inline bool Object_release(Object* self); +inline void Object_promoteToShared(Object* self); +inline void Object_create(Object* self, objectType type); +inline uword_t Object_hash(Object* self); +inline bool Object_equals(Object* self, Object* other); +inline bool Object_isReusable(Object* self); +inline bool isReusable(RTValue v); +inline void Object_promoteToSharedShallow(Object* self, uword_t current); +inline uword_t Object_getRawRefCount(Object* self); +inline bool Object_release_internal(Object* self, bool deallocateChildren); +inline void Ptr_retain(void* ptr); +inline bool Ptr_release(void* ptr); +inline void Ptr_autorelease(void* ptr); +inline uword_t Ptr_hash(void* ptr); +inline bool Ptr_equals(void* ptr, void* other); +inline bool Ptr_isReusable(void* ptr); #include "BigInteger.h" @@ -112,7 +112,7 @@ static inline bool Ptr_isReusable(void* ptr); #include "Symbol.h" #include "Var.h" -static inline void *allocate(size_t size) { +inline void *allocate(size_t size) { #ifndef USE_MEMORY_BANKS return malloc(size); #else @@ -169,7 +169,7 @@ static inline void *allocate(size_t size) { #endif } -static inline void deallocate(void *ptr) { +inline void deallocate(void *ptr) { #ifndef USE_MEMORY_BANKS free(ptr); return; @@ -193,7 +193,7 @@ static inline void deallocate(void *ptr) { } /* Outside of refcount system */ -static inline objectType getType(RTValue v) { +inline objectType getType(RTValue v) { if (RT_isDouble(v)) return doubleType; if (RT_isInt32(v)) @@ -210,7 +210,7 @@ static inline objectType getType(RTValue v) { return ((Object *)RT_unboxPtr(v))->type; } -static inline void Object_retain(Object *restrict self) { +inline void Object_retain(Object *restrict self) { // printf("RETAIN!!! %d\n", self->type); #ifdef REFCOUNT_TRACING atomic_fetch_add_explicit(&(allocationCount[self->type - 1]), 1, @@ -236,7 +236,7 @@ static inline void Object_retain(Object *restrict self) { #endif } -static inline void Object_destroy(Object *restrict self, bool deallocateChildren) { +inline void Object_destroy(Object *restrict self, bool deallocateChildren) { // printf("--> Deallocating type %d addres %lld\n", self->type, (uword_t)); // printReferenceCounts(); switch ((objectType)self->type) { @@ -285,7 +285,7 @@ static inline void Object_destroy(Object *restrict self, bool deallocateChildren // printf("=========================\n"); } -static inline bool Object_isReusable(Object *restrict self) { +inline bool Object_isReusable(Object *restrict self) { uword_t refCount = atomic_load_explicit(&(self->atomicRefCount), memory_order_acquire); if (refCount & SHARED_BIT) @@ -293,11 +293,11 @@ static inline bool Object_isReusable(Object *restrict self) { return refCount >> 1 == 1; } -static inline bool isReusable(RTValue self) { +inline bool isReusable(RTValue self) { return RT_isPtr(self) && Object_isReusable((Object *)RT_unboxPtr(self)); } -static inline bool Object_release_internal(Object *restrict self, +inline bool Object_release_internal(Object *restrict self, bool deallocateChildren) { #ifdef REFCOUNT_TRACING atomic_fetch_sub_explicit(&(allocationCount[self->type - 1]), 1, @@ -356,7 +356,7 @@ static inline bool Object_release_internal(Object *restrict self, } /* outside of refcount system */ -static inline void Object_promoteToSharedShallow(Object *restrict self, +inline void Object_promoteToSharedShallow(Object *restrict self, uword_t current) { #ifndef REFCOUNT_NONATOMIC // Optimization: If we guarantee that only the 'owning' thread performs @@ -372,12 +372,12 @@ static inline void Object_promoteToSharedShallow(Object *restrict self, } /* outside of refcount system */ -static inline uword_t Object_getRawRefCount(Object *self) { +inline uword_t Object_getRawRefCount(Object *self) { return atomic_load_explicit(&self->atomicRefCount, memory_order_relaxed); } /* outside of refcount system */ -static inline void Object_promoteToShared(Object *restrict self) { +inline void Object_promoteToShared(Object *restrict self) { uword_t count = Object_getRawRefCount(self); if ((count & SHARED_BIT) != 0) { return; @@ -408,7 +408,7 @@ static inline void Object_promoteToShared(Object *restrict self) { /* Outside of refcount system */ -static inline uword_t Object_hash(Object *restrict self) { +inline uword_t Object_hash(Object *restrict self) { switch ((objectType)self->type) { case stringType: return String_hash((String *)self); @@ -446,7 +446,7 @@ static inline uword_t Object_hash(Object *restrict self) { } /* Outside of refcount system */ -static inline uword_t hash(RTValue v) { +inline uword_t hash(RTValue v) { objectType t = getType(v); switch (t) { case integerType: @@ -473,7 +473,7 @@ static inline uword_t hash(RTValue v) { } /* Outside of refcount system */ -static inline bool Object_equals(Object *self, Object *other) { +inline bool Object_equals(Object *self, Object *other) { if (Object_hash(self) != Object_hash(other)) return false; @@ -527,7 +527,7 @@ static inline bool Object_equals(Object *self, Object *other) { /* Outside of refcount system - should it be like this? It probably shouldnt */ -static inline bool equals(RTValue self, RTValue other) { +inline bool equals(RTValue self, RTValue other) { if (self == other) return true; // Handles same-ints, same-bools, and same-pointers @@ -547,7 +547,7 @@ static inline bool equals(RTValue self, RTValue other) { return false; } -static inline String *Object_toString(Object *restrict self) { +inline String *Object_toString(Object *restrict self) { switch ((objectType)self->type) { case stringType: return String_toString((String *)self); @@ -583,11 +583,11 @@ static inline String *Object_toString(Object *restrict self) { return NULL; } -static inline bool Object_release(Object *restrict self) { +inline bool Object_release(Object *restrict self) { return Object_release_internal(self, true); } -static inline void Object_autorelease(Object *restrict self) { +inline void Object_autorelease(Object *restrict self) { /* The object could have been deallocated through direct releases in the * meantime (e.g. if autoreleasing entity does not own ) */ #ifdef REFCOUNT_NONATOMIC @@ -602,32 +602,32 @@ static inline void Object_autorelease(Object *restrict self) { Object_release(self); } -static inline void autorelease(RTValue self) { +inline void autorelease(RTValue self) { if (RT_isPtr(self)) { Object_autorelease((Object *)RT_unboxPtr(self)); } } -static inline void retain(RTValue self) { +inline void retain(RTValue self) { if (RT_isPtr(self)) { Object_retain((Object *)RT_unboxPtr(self)); } } -static inline bool release(RTValue self) { +inline bool release(RTValue self) { if (RT_isPtr(self)) { return Object_release((Object *)RT_unboxPtr(self)); } return false; } -static inline void promoteToShared(RTValue self) { +inline void promoteToShared(RTValue self) { if (RT_isPtr(self)) { Object_promoteToShared((Object *)RT_unboxPtr(self)); } } -static inline void Object_create(Object *restrict self, objectType type) { +inline void Object_create(Object *restrict self, objectType type) { #ifdef REFCOUNT_NONATOMIC self->refCount = COUNT_INC; #else @@ -643,25 +643,25 @@ static inline void Object_create(Object *restrict self, objectType type) { // printf("--> Allocating type %d addres %p\n", self->type, ); } -static inline uword_t combineHash(uword_t lhs, uword_t rhs) { +inline uword_t combineHash(uword_t lhs, uword_t rhs) { lhs ^= rhs + 0x9ddfea08eb382d69ULL + (lhs << 6) + (lhs >> 2); return lhs; } -static inline void Ptr_autorelease(void *self) { Object_autorelease((Object *)self); } -static inline void Ptr_retain(void *self) { Object_retain((Object *)self); } -static inline bool Ptr_release(void *self) { return Object_release((Object *)self); } -static inline uword_t Ptr_hash(void *self) { return Object_hash((Object *)self); } -static inline bool Ptr_isReusable(void *self) { +inline void Ptr_autorelease(void *self) { Object_autorelease((Object *)self); } +inline void Ptr_retain(void *self) { Object_retain((Object *)self); } +inline bool Ptr_release(void *self) { return Object_release((Object *)self); } +inline uword_t Ptr_hash(void *self) { return Object_hash((Object *)self); } +inline bool Ptr_isReusable(void *self) { return Object_isReusable((Object *)self); } -static inline bool Ptr_equals(void *self, void *other) { +inline bool Ptr_equals(void *self, void *other) { if (self == other) return true; return Object_equals((Object *)self, (Object *)other); } -static inline String *toString(RTValue self) { +inline String *toString(RTValue self) { if (RT_isInt32(self)) return Integer_toString(self); if (RT_isDouble(self)) @@ -681,7 +681,7 @@ static inline String *toString(RTValue self) { return Object_toString((Object *)RT_unboxPtr(self)); } -static inline bool equals_managed(RTValue self, RTValue other) { +inline bool equals_managed(RTValue self, RTValue other) { bool result = equals(self, other); release(self); release(other); diff --git a/backend-v2/tests/CMakeLists.txt b/backend-v2/tests/CMakeLists.txt index 0cdef3e9..792a1e07 100644 --- a/backend-v2/tests/CMakeLists.txt +++ b/backend-v2/tests/CMakeLists.txt @@ -14,8 +14,10 @@ set(TEST_MODULES codegen/MathIntrinsics_test codegen/DoubleAddRepro_test codegen/NoThrow_test + codegen/DoNode_test codegen/LetNode_test - codegen/LetLeak_test + codegen/MathIntrinsicsRepro_test +# codegen/MathSpecialization_test VarUAF_repro_test ) diff --git a/backend-v2/tests/codegen/DoNode_test.cpp b/backend-v2/tests/codegen/DoNode_test.cpp new file mode 100644 index 00000000..65fba2ae --- /dev/null +++ b/backend-v2/tests/codegen/DoNode_test.cpp @@ -0,0 +1,101 @@ +#include "../../RuntimeHeaders.h" +#include "../../jit/JITEngine.h" +#include "../../state/ThreadsafeCompilerState.h" +#include "../../tools/EdnParser.h" +#include "bytecode.pb.h" +#include + +extern "C" { +#include "../../runtime/RuntimeInterface.h" +#include "../../runtime/tests/TestTools.h" +#include +} + +using namespace std; +using namespace rt; +using namespace clojure::rt::protobuf::bytecode; + +extern "C" { +RTValue mock_add_bigint(RTValue a, RTValue b) { + release(a); + release(b); + return RT_boxPtr(BigInteger_createFromInt(42)); +} +} + +static void test_do_statement_leak(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + + // (do (+ 1N 2N) 42) + Node root; + root.set_op(opDo); + auto *do_ = root.mutable_subnode()->mutable_do_(); + + auto *stmt = do_->add_statements(); + stmt->set_op(opStaticCall); + auto *sc = stmt->mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("add"); + + auto *a1 = sc->add_args(); + a1->set_op(opConst); + a1->mutable_subnode()->mutable_const_()->set_val("1"); + a1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + a1->set_tag("clojure.lang.BigInt"); + + auto *a2 = sc->add_args(); + a2->set_op(opConst); + a2->mutable_subnode()->mutable_const_()->set_val("2"); + a2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + a2->set_tag("clojure.lang.BigInt"); + + auto *ret = do_->mutable_ret(); + ret->set_op(opConst); + ret->mutable_subnode()->mutable_const_()->set_val("42"); + ret->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + ret->set_tag("long"); + + // Setup clojure.lang.Numbers/add metadata + auto desc = std::make_unique(); + rt::IntrinsicDescription numAdd; + numAdd.type = rt::CallType::Call; + numAdd.symbol = "mock_add_bigint"; + numAdd.argTypes.push_back(rt::ObjectTypeSet::all()); + numAdd.argTypes.push_back(rt::ObjectTypeSet::all()); + numAdd.returnType = rt::ObjectTypeSet::all(); + desc->staticFns["add"].push_back(numAdd); + + String *numName = String_createDynamicStr("clojure.lang.Numbers"); + Ptr_retain(numName); + ::Class *numCls = Class_create(numName, numName, 0, nullptr); + numCls->compilerExtension = desc.release(); + numCls->compilerExtensionDestructor = rt::delete_class_description; + compState.classRegistry.registerObject("clojure.lang.Numbers", numCls); + + cout << "=== Do Statement Leak Test (Should NOT leak) ===" << endl; + try { + rt::JITEngine engine(compState); + auto res = engine.compileAST(root, "__test_do_leak", llvm::OptimizationLevel::O0, true).get(); + + RTValue val = res.toPtr()(); + assert_int_equal(42, RT_unboxInt32(val)); + } catch (const std::exception &e) { + cout << "Caught exception: " << e.what() << endl; + assert_true(false); + } + }); +} + +int main(void) { + initialise_memory(); + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_do_statement_leak), + }; + + int result = cmocka_run_group_tests(tests, NULL, NULL); + RuntimeInterface_cleanup(); + return result; +} diff --git a/backend-v2/tests/codegen/IfNode_test.cpp b/backend-v2/tests/codegen/IfNode_test.cpp index cdd02991..ff798be6 100644 --- a/backend-v2/tests/codegen/IfNode_test.cpp +++ b/backend-v2/tests/codegen/IfNode_test.cpp @@ -19,6 +19,14 @@ using namespace std; using namespace rt; using namespace clojure::rt::protobuf::bytecode; +extern "C" { +RTValue mock_add_bigint(RTValue a, RTValue b) { + release(a); + release(b); + return RT_boxPtr(BigInteger_createFromInt(42)); +} +} + static RTValue resPtrToValue(llvm::orc::ExecutorAddr res) { return res.toPtr()(); } @@ -185,6 +193,78 @@ static void test_if_integer_const(void **state) { }); } +static void test_if_test_leak(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + + // (if (+ 1N 2N) 42 43) + Node root; + root.set_op(opIf); + auto *if_ = root.mutable_subnode()->mutable_if_(); + + auto *testNode = if_->mutable_test(); + testNode->set_op(opStaticCall); + auto *sc = testNode->mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("add"); + + auto *a1 = sc->add_args(); + a1->set_op(opConst); + a1->mutable_subnode()->mutable_const_()->set_val("1"); + a1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + a1->set_tag("clojure.lang.BigInt"); + + auto *a2 = sc->add_args(); + a2->set_op(opConst); + a2->mutable_subnode()->mutable_const_()->set_val("2"); + a2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + a2->set_tag("clojure.lang.BigInt"); + + auto *then = if_->mutable_then(); + then->set_op(opConst); + then->mutable_subnode()->mutable_const_()->set_val("42"); + then->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + then->set_tag("long"); + + auto *else_ = if_->mutable_else_(); + else_->set_op(opConst); + else_->mutable_subnode()->mutable_const_()->set_val("43"); + else_->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + else_->set_tag("long"); + + // Setup clojure.lang.Numbers/add metadata + auto desc = std::make_unique(); + rt::IntrinsicDescription numAdd; + numAdd.type = rt::CallType::Call; + numAdd.symbol = "mock_add_bigint"; + numAdd.argTypes.push_back(rt::ObjectTypeSet::all()); + numAdd.argTypes.push_back(rt::ObjectTypeSet::all()); + numAdd.returnType = rt::ObjectTypeSet::all(); + desc->staticFns["add"].push_back(numAdd); + + String *numName = String_createDynamicStr("clojure.lang.Numbers"); + Ptr_retain(numName); + ::Class *numCls = Class_create(numName, numName, 0, nullptr); + numCls->compilerExtension = desc.release(); + numCls->compilerExtensionDestructor = rt::delete_class_description; + compState.classRegistry.registerObject("clojure.lang.Numbers", numCls); + + cout << "=== If Test Leak Test (Should NOT leak) ===" << endl; + try { + rt::JITEngine engine(compState); + auto res = engine.compileAST(root, "__test_if_leak", llvm::OptimizationLevel::O0, true).get(); + + RTValue val = res.toPtr()(); + assert_int_equal(42, RT_unboxInt32(val)); + } catch (const std::exception &e) { + cout << "Caught exception: " << e.what() << endl; + assert_true(false); + } + }); +} + int main(void) { initialise_memory(); const struct CMUnitTest tests[] = { @@ -192,6 +272,7 @@ int main(void) { cmocka_unit_test(test_if_falsy_const), cmocka_unit_test(test_if_nil), cmocka_unit_test(test_if_integer_const), + cmocka_unit_test(test_if_test_leak), }; int result = cmocka_run_group_tests(tests, NULL, NULL); diff --git a/backend-v2/tests/codegen/LetLeak_test.cpp b/backend-v2/tests/codegen/LetLeak_test.cpp deleted file mode 100644 index b6840b1b..00000000 --- a/backend-v2/tests/codegen/LetLeak_test.cpp +++ /dev/null @@ -1,357 +0,0 @@ -#include "../../RuntimeHeaders.h" -#include "../../jit/JITEngine.h" -#include "../../state/ThreadsafeCompilerState.h" -#include "../../tools/EdnParser.h" -#include "bytecode.pb.h" -#include - -extern "C" { -#include "../../runtime/RuntimeInterface.h" -#include "../../runtime/tests/TestTools.h" -#include -} - -using namespace std; -using namespace rt; -using namespace clojure::rt::protobuf::bytecode; - -extern "C" { -RTValue mock_add_bigint(RTValue a, RTValue b) { - // We don't really care about the result value, just that it's a BigInt - // that needs to be released. - release(a); - release(b); - return RT_boxPtr(BigInteger_createFromInt(42)); -} -} - -static void test_let_uaf_fixed(void **state) { - (void)state; - rt::ThreadsafeCompilerState compState; - rt::JITEngine engine(compState); - - // (let [x 1N] (+ "aa" x)) - // This used to cause UAF because both let and Numbers_add released x. - Node root; - root.set_op(opLet); - auto *let = root.mutable_subnode()->mutable_let(); - - auto *bx = let->add_bindings(); - bx->set_op(opBinding); - auto *bxc = bx->mutable_subnode()->mutable_binding(); - bxc->set_name("x"); - bxc->mutable_init()->set_op(opConst); - bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_val("1"); - bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); - bxc->mutable_init()->set_tag("clojure.lang.BigInt"); - - auto *body = let->mutable_body(); - body->set_op(opStaticCall); - auto *sc = body->mutable_subnode()->mutable_staticcall(); - sc->set_class_("rt.Core"); - sc->set_method("Numbers_add"); - - auto *arg1 = sc->add_args(); - arg1->set_op(opConst); - arg1->mutable_subnode()->mutable_const_()->set_val("aa"); - arg1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); - - auto *arg2 = sc->add_args(); - arg2->set_op(opLocal); - arg2->mutable_subnode()->mutable_local()->set_name("x"); - arg2->mutable_subnode()->mutable_local()->set_local(localTypeLet); - - // Setup rt.Core/Numbers_add metadata - auto desc = std::make_unique(); - rt::IntrinsicDescription numAdd; - numAdd.type = rt::CallType::Call; - numAdd.symbol = "Numbers_add"; - numAdd.argTypes = {rt::ObjectTypeSet::all(), rt::ObjectTypeSet::all()}; - numAdd.returnType = rt::ObjectTypeSet::all(); - desc->staticFns["Numbers_add"].push_back(numAdd); - - String *coreName = String_createDynamicStr("rt.Core"); - Ptr_retain(coreName); - ::Class *coreCls = Class_create(coreName, coreName, 0, nullptr); - coreCls->compilerExtension = desc.release(); - coreCls->compilerExtensionDestructor = rt::delete_class_description; - compState.classRegistry.registerObject("rt.Core", coreCls); - - cout << "=== Let UAF Fixed Test (Should throw LanguageException but NOT crash) ===" << endl; - uword_t initialCount = atomic_load(&objectCount[bigIntegerType - 1]); - try { - { - rt::JITEngine engine(compState); - auto res = engine.compileAST(root, "__test_let_uaf", llvm::OptimizationLevel::O0, true).get(); - res.toPtr()(); - assert_true(false); // Should have thrown - } - } catch (const rt::LanguageException &e) { - cout << "Caught expected LanguageException: " << rt::getExceptionString(e) << endl; - } - uword_t finalCount = atomic_load(&objectCount[bigIntegerType - 1]); - cout << "BigInt initial: " << initialCount << ", final: " << finalCount << endl; - // In the fixed case, there should be no additional BigInts left. - assert_int_equal(finalCount, initialCount); -} - -static void test_let_gap_leak(void **state) { - (void)state; - rt::ThreadsafeCompilerState compState; - rt::JITEngine engine(compState); - - // (let [x 1N] (do (+ "aa" "bb") x)) - // The first Numbers_add throws. x is never consumed. x is NOT protected by let anymore. - // This SHOULD leak. - Node root; - root.set_op(opLet); - auto *let = root.mutable_subnode()->mutable_let(); - - auto *bx = let->add_bindings(); - bx->set_op(opBinding); - auto *bxc = bx->mutable_subnode()->mutable_binding(); - bxc->set_name("x"); - bxc->mutable_init()->set_op(opConst); - bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_val("1"); - bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); - bxc->mutable_init()->set_tag("clojure.lang.BigInt"); - - auto *body = let->mutable_body(); - body->set_op(opDo); - auto *do_ = body->mutable_subnode()->mutable_do_(); - - auto *stmt = do_->add_statements(); - stmt->set_op(opStaticCall); - auto *sc = stmt->mutable_subnode()->mutable_staticcall(); - sc->set_class_("rt.Core"); - sc->set_method("Numbers_add"); - auto *a1 = sc->add_args(); - a1->set_op(opConst); - a1->mutable_subnode()->mutable_const_()->set_val("aa"); - a1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); - auto *a2 = sc->add_args(); - a2->set_op(opConst); - a2->mutable_subnode()->mutable_const_()->set_val("bb"); - a2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); - - auto *ret = do_->mutable_ret(); - ret->set_op(opLocal); - ret->mutable_subnode()->mutable_local()->set_name("x"); - ret->mutable_subnode()->mutable_local()->set_local(localTypeLet); - - // Add unwind-memory guidance to simulate real compiler output - // If Numbers_add throws, x must be released. - auto* g1 = body->add_unwindmemory(); - g1->set_variablename("x"); - g1->set_requiredrefcountchange(-1); - - auto* g2 = stmt->add_unwindmemory(); - g2->set_variablename("x"); - g2->set_requiredrefcountchange(-1); - - auto* g3 = ret->add_unwindmemory(); - g3->set_variablename("x"); - g3->set_requiredrefcountchange(-1); - - auto* g4 = a1->add_unwindmemory(); - g4->set_variablename("x"); - g4->set_requiredrefcountchange(-1); - - auto* g5 = a2->add_unwindmemory(); - g5->set_variablename("x"); - g5->set_requiredrefcountchange(-1); - - // Setup rt.Core/Numbers_add metadata - auto desc = std::make_unique(); - rt::IntrinsicDescription numAdd; - numAdd.type = rt::CallType::Call; - numAdd.symbol = "Numbers_add"; - numAdd.argTypes = {rt::ObjectTypeSet::all(), rt::ObjectTypeSet::all()}; - numAdd.returnType = rt::ObjectTypeSet::all(); - desc->staticFns["Numbers_add"].push_back(numAdd); - - String *coreName = String_createDynamicStr("rt.Core"); - Ptr_retain(coreName); - ::Class *coreCls = Class_create(coreName, coreName, 0, nullptr); - coreCls->compilerExtension = desc.release(); - coreCls->compilerExtensionDestructor = rt::delete_class_description; - compState.classRegistry.registerObject("rt.Core", coreCls); - - cout << "=== Let Gap Leak Test (Should leak 1N) ===" << endl; - uword_t initialCount = atomic_load(&objectCount[bigIntegerType - 1]); - try { - { - rt::JITEngine engine(compState); - auto res = engine.compileAST(root, "__test_let_leak", llvm::OptimizationLevel::O0, true).get(); - res.toPtr()(); - assert_true(false); // Should have thrown - } - } catch (const rt::LanguageException &e) { - cout << "Caught expected LanguageException: " << rt::getExceptionString(e) << endl; - } - uword_t finalCount = atomic_load(&objectCount[bigIntegerType - 1]); - cout << "BigInt initial: " << initialCount << ", final: " << finalCount << endl; - // This should now NOT leak because unwind-memory handles the release during exceptions. - assert_int_equal(finalCount, initialCount); -} - -static void test_do_statement_leak(void **state) { - (void)state; - rt::ThreadsafeCompilerState compState; - rt::JITEngine engine(compState); - - // (do (+ 1N 2N) 42) - // The result of (+ 1N 2N) is a BigInt statement that must be released. - Node root; - root.set_op(opDo); - auto *do_ = root.mutable_subnode()->mutable_do_(); - - auto *stmt = do_->add_statements(); - stmt->set_op(opStaticCall); - auto *sc = stmt->mutable_subnode()->mutable_staticcall(); - sc->set_class_("clojure.lang.Numbers"); - sc->set_method("add"); - - auto *a1 = sc->add_args(); - a1->set_op(opConst); - a1->mutable_subnode()->mutable_const_()->set_val("1"); - a1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); - a1->set_tag("clojure.lang.BigInt"); - - auto *a2 = sc->add_args(); - a2->set_op(opConst); - a2->mutable_subnode()->mutable_const_()->set_val("2"); - a2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); - a2->set_tag("clojure.lang.BigInt"); - - auto *ret = do_->mutable_ret(); - ret->set_op(opConst); - ret->mutable_subnode()->mutable_const_()->set_val("42"); - ret->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); - ret->set_tag("long"); - - // Setup clojure.lang.Numbers/add metadata - auto desc = std::make_unique(); - rt::IntrinsicDescription numAdd; - numAdd.type = rt::CallType::Call; - numAdd.symbol = "mock_add_bigint"; - numAdd.argTypes = {rt::ObjectTypeSet::all(), rt::ObjectTypeSet::all()}; - numAdd.returnType = rt::ObjectTypeSet::all(); - desc->staticFns["add"].push_back(numAdd); - - String *numName = String_createDynamicStr("clojure.lang.Numbers"); - Ptr_retain(numName); - ::Class *numCls = Class_create(numName, numName, 0, nullptr); - numCls->compilerExtension = desc.release(); - numCls->compilerExtensionDestructor = rt::delete_class_description; - compState.classRegistry.registerObject("clojure.lang.Numbers", numCls); - - cout << "=== Do Statement Leak Test (Should NOT leak) ===" << endl; - uword_t initialCount = atomic_load(&objectCount[bigIntegerType - 1]); - { - rt::JITEngine engine(compState); - auto res = engine.compileAST(root, "__test_do_leak", llvm::OptimizationLevel::O0, true).get(); - - // Account for constants created during compilation - uword_t afterCompileCount = atomic_load(&objectCount[bigIntegerType - 1]); - cout << "BigInt initial: " << initialCount << ", after compile: " << afterCompileCount << endl; - - RTValue val = res.toPtr()(); - assert_int_equal(42, RT_unboxInt32(val)); - - uword_t finalCount = atomic_load(&objectCount[bigIntegerType - 1]); - cout << "BigInt final: " << finalCount << endl; - - // The statement result should have been released. - // Any BigInts created in the mock should be gone. - assert_int_equal(finalCount, afterCompileCount); - } -} - -static void test_if_test_leak(void **state) { - (void)state; - rt::ThreadsafeCompilerState compState; - rt::JITEngine engine(compState); - - // (if (+ 1N 2N) 42 43) - // The result of (+ 1N 2N) is used as a test condition and must be released. - Node root; - root.set_op(opIf); - auto *if_ = root.mutable_subnode()->mutable_if_(); - - auto *testNode = if_->mutable_test(); - testNode->set_op(opStaticCall); - auto *sc = testNode->mutable_subnode()->mutable_staticcall(); - sc->set_class_("clojure.lang.Numbers"); - sc->set_method("add"); - - auto *a1 = sc->add_args(); - a1->set_op(opConst); - a1->mutable_subnode()->mutable_const_()->set_val("1"); - a1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); - a1->set_tag("clojure.lang.BigInt"); - - auto *a2 = sc->add_args(); - a2->set_op(opConst); - a2->mutable_subnode()->mutable_const_()->set_val("2"); - a2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); - a2->set_tag("clojure.lang.BigInt"); - - auto *then = if_->mutable_then(); - then->set_op(opConst); - then->mutable_subnode()->mutable_const_()->set_val("42"); - then->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); - then->set_tag("long"); - - auto *else_ = if_->mutable_else_(); - else_->set_op(opConst); - else_->mutable_subnode()->mutable_const_()->set_val("43"); - else_->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); - else_->set_tag("long"); - - // Setup clojure.lang.Numbers/add metadata - auto desc = std::make_unique(); - rt::IntrinsicDescription numAdd; - numAdd.type = rt::CallType::Call; - numAdd.symbol = "mock_add_bigint"; - numAdd.argTypes = {rt::ObjectTypeSet::all(), rt::ObjectTypeSet::all()}; - numAdd.returnType = rt::ObjectTypeSet::all(); - desc->staticFns["add"].push_back(numAdd); - - String *numName = String_createDynamicStr("clojure.lang.Numbers"); - Ptr_retain(numName); - ::Class *numCls = Class_create(numName, numName, 0, nullptr); - numCls->compilerExtension = desc.release(); - numCls->compilerExtensionDestructor = rt::delete_class_description; - compState.classRegistry.registerObject("clojure.lang.Numbers", numCls); - - cout << "=== If Test Leak Test (Should NOT leak) ===" << endl; - uword_t initialCount = atomic_load(&objectCount[bigIntegerType - 1]); - (void)initialCount; // Silence warning - { - rt::JITEngine engine(compState); - auto res = engine.compileAST(root, "__test_if_leak", llvm::OptimizationLevel::O0, true).get(); - - uword_t afterCompileCount = atomic_load(&objectCount[bigIntegerType - 1]); - RTValue val = res.toPtr()(); - assert_int_equal(42, RT_unboxInt32(val)); - - uword_t finalCount = atomic_load(&objectCount[bigIntegerType - 1]); - cout << "BigInt after compile: " << afterCompileCount << ", final: " << finalCount << endl; - assert_int_equal(finalCount, afterCompileCount); - } -} - -int main(void) { - initialise_memory(); - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_let_uaf_fixed), - cmocka_unit_test(test_let_gap_leak), - cmocka_unit_test(test_do_statement_leak), - cmocka_unit_test(test_if_test_leak), - }; - - int result = cmocka_run_group_tests(tests, NULL, NULL); - RuntimeInterface_cleanup(); - return result; -} diff --git a/backend-v2/tests/codegen/LetNode_test.cpp b/backend-v2/tests/codegen/LetNode_test.cpp index 8f00109f..c46fb3a8 100644 --- a/backend-v2/tests/codegen/LetNode_test.cpp +++ b/backend-v2/tests/codegen/LetNode_test.cpp @@ -15,133 +15,363 @@ using namespace std; using namespace rt; using namespace clojure::rt::protobuf::bytecode; +extern "C" { +RTValue mock_add_bigint(RTValue a, RTValue b) { + // We don't really care about the result value, just that it's a BigInt + // that needs to be released. + release(a); + release(b); + return RT_boxPtr(BigInteger_createFromInt(42)); +} +} + static void test_let_basic(void **state) { (void)state; - rt::ThreadsafeCompilerState compState; - rt::JITEngine engine(compState); - - // (let [x 1 y 2] (+ x y)) - Node root; - root.set_op(opLet); - auto *let = root.mutable_subnode()->mutable_let(); - - // Binding x = 1 - auto *bx = let->add_bindings(); - bx->set_op(opBinding); - auto *bxc = bx->mutable_subnode()->mutable_binding(); - bxc->set_name("x"); - bxc->mutable_init()->set_op(opConst); - bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_val("1"); - bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); - - // Binding y = 2 - auto *by = let->add_bindings(); - by->set_op(opBinding); - auto *byc = by->mutable_subnode()->mutable_binding(); - byc->set_name("y"); - byc->mutable_init()->set_op(opConst); - byc->mutable_init()->mutable_subnode()->mutable_const_()->set_val("2"); - byc->mutable_init()->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); - - // Body: (StaticCall rt.Core/Numbers_add x y) - auto *body = let->mutable_body(); - body->set_op(opStaticCall); - auto *sc = body->mutable_subnode()->mutable_staticcall(); - sc->set_class_("rt.Core"); - sc->set_method("Numbers_add"); - - auto *arg1 = sc->add_args(); - arg1->set_op(opLocal); - arg1->mutable_subnode()->mutable_local()->set_name("x"); - arg1->mutable_subnode()->mutable_local()->set_local(localTypeLet); - - auto *arg2 = sc->add_args(); - arg2->set_op(opLocal); - arg2->mutable_subnode()->mutable_local()->set_name("y"); - arg2->mutable_subnode()->mutable_local()->set_local(localTypeLet); - - // Setup rt.Core/Numbers_add metadata - auto desc = std::make_unique(); - rt::IntrinsicDescription numAdd; - numAdd.type = rt::CallType::Call; - numAdd.symbol = "Numbers_add"; - numAdd.argTypes = {rt::ObjectTypeSet::all(), rt::ObjectTypeSet::all()}; - numAdd.returnType = rt::ObjectTypeSet::all(); - desc->staticFns["Numbers_add"].push_back(numAdd); - - String *coreName = String_createDynamicStr("rt.Core"); - Ptr_retain(coreName); - ::Class *coreCls = Class_create(coreName, coreName, 0, nullptr); - coreCls->compilerExtension = desc.release(); - coreCls->compilerExtensionDestructor = rt::delete_class_description; - compState.classRegistry.registerObject("rt.Core", coreCls); - - try { - auto res = engine.compileAST(root, "__test_let_basic", llvm::OptimizationLevel::O0, true).get(); - RTValue result = res.toPtr()(); - assert_int_equal(RT_unboxInt32(result), 3); - } catch (const rt::LanguageException &e) { - cout << "Caught LanguageException: " << rt::getExceptionString(e) << endl; - assert_true(false); - } catch (const std::exception &e) { - cout << "Caught exception: " << e.what() << endl; - assert_true(false); - } + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + + // (let [x 1 y 2] (+ x y)) + Node root; + root.set_op(opLet); + auto *let = root.mutable_subnode()->mutable_let(); + + // Binding x = 1 + auto *bx = let->add_bindings(); + bx->set_op(opBinding); + auto *bxc = bx->mutable_subnode()->mutable_binding(); + bxc->set_name("x"); + bxc->mutable_init()->set_op(opConst); + bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_val("1"); + bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + + // Binding y = 2 + auto *by = let->add_bindings(); + by->set_op(opBinding); + auto *byc = by->mutable_subnode()->mutable_binding(); + byc->set_name("y"); + byc->mutable_init()->set_op(opConst); + byc->mutable_init()->mutable_subnode()->mutable_const_()->set_val("2"); + byc->mutable_init()->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + + // Body: (StaticCall rt.Core/Numbers_add x y) + auto *body = let->mutable_body(); + body->set_op(opStaticCall); + auto *sc = body->mutable_subnode()->mutable_staticcall(); + sc->set_class_("rt.Core"); + sc->set_method("Numbers_add"); + + auto *arg1 = sc->add_args(); + arg1->set_op(opLocal); + arg1->mutable_subnode()->mutable_local()->set_name("x"); + arg1->mutable_subnode()->mutable_local()->set_local(localTypeLet); + + auto *arg2 = sc->add_args(); + arg2->set_op(opLocal); + arg2->mutable_subnode()->mutable_local()->set_name("y"); + arg2->mutable_subnode()->mutable_local()->set_local(localTypeLet); + + // Setup rt.Core/Numbers_add metadata + auto desc = std::make_unique(); + rt::IntrinsicDescription numAdd; + numAdd.type = rt::CallType::Call; + numAdd.symbol = "Numbers_add"; + numAdd.argTypes.push_back(rt::ObjectTypeSet::all()); + numAdd.argTypes.push_back(rt::ObjectTypeSet::all()); + numAdd.returnType = rt::ObjectTypeSet::all(); + desc->staticFns["Numbers_add"].push_back(numAdd); + + String *coreName = String_createDynamicStr("rt.Core"); + Ptr_retain(coreName); + ::Class *coreCls = Class_create(coreName, coreName, 0, nullptr); + coreCls->compilerExtension = desc.release(); + coreCls->compilerExtensionDestructor = rt::delete_class_description; + compState.classRegistry.registerObject("rt.Core", coreCls); + + try { + auto res = engine.compileAST(root, "__test_let_basic", llvm::OptimizationLevel::O0, true).get(); + RTValue result = res.toPtr()(); + assert_int_equal(RT_unboxInt32(result), 3); + } catch (const rt::LanguageException &e) { + cout << "Caught LanguageException: " << rt::getExceptionString(e) << endl; + assert_true(false); + } catch (const std::exception &e) { + cout << "Caught exception: " << e.what() << endl; + assert_true(false); + } + }); } static void test_let_memory_mm(void **state) { (void)state; - rt::ThreadsafeCompilerState compState; - rt::JITEngine engine(compState); - - // (let [x "hello"] x) - // With guidance to retain x twice and release once - Node root; - root.set_op(opLet); - auto *let = root.mutable_subnode()->mutable_let(); - - // Binding x = "hello" - auto *bx = let->add_bindings(); - bx->set_op(opBinding); - auto *bxc = bx->mutable_subnode()->mutable_binding(); - bxc->set_name("x"); - bxc->mutable_init()->set_op(opConst); - bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_val("hello"); - bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); - - // Guidance for the binding - auto *g1 = bx->add_dropmemory(); - g1->set_variablename("x"); - g1->set_requiredrefcountchange(1); // retain x - - // Body: x - auto *body = let->mutable_body(); - body->set_op(opLocal); - body->mutable_subnode()->mutable_local()->set_name("x"); - body->mutable_subnode()->mutable_local()->set_local(localTypeLet); - - // Guidance for the body (before body evaluation) - auto *g2 = root.add_dropmemory(); - g2->set_variablename("x"); - g2->set_requiredrefcountchange(-1); // release x - - cout << "=== Let Memory Guidance Test IR ===" << endl; - try { - auto res = engine.compileAST(root, "__test_let_mm", llvm::OptimizationLevel::O0, true).get(); - cout << "===================================" << endl; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + + // (let [x "hello"] x) + // With guidance to retain x twice and release once + Node root; + root.set_op(opLet); + auto *let = root.mutable_subnode()->mutable_let(); + + // Binding x = "hello" + auto *bx = let->add_bindings(); + bx->set_op(opBinding); + auto *bxc = bx->mutable_subnode()->mutable_binding(); + bxc->set_name("x"); + bxc->mutable_init()->set_op(opConst); + bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_val("hello"); + bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); + + // Guidance for the binding + auto *g1 = bx->add_dropmemory(); + g1->set_variablename("x"); + g1->set_requiredrefcountchange(1); // retain x + + // Body: x + auto *body = let->mutable_body(); + body->set_op(opLocal); + body->mutable_subnode()->mutable_local()->set_name("x"); + body->mutable_subnode()->mutable_local()->set_local(localTypeLet); + + // Guidance for the body (before body evaluation) + auto *g2 = root.add_dropmemory(); + g2->set_variablename("x"); + g2->set_requiredrefcountchange(-1); // release x + + cout << "=== Let Memory Guidance Test IR ===" << endl; + try { + auto res = engine.compileAST(root, "__test_let_mm", llvm::OptimizationLevel::O0, true).get(); + cout << "===================================" << endl; + + RTValue result = res.toPtr()(); + assert_true(RT_isPtr(result)); + String *s = (String *)RT_unboxPtr(result); + assert_string_equal(String_c_str(s), "hello"); + release(result); + } catch (const rt::LanguageException &e) { + cout << "Caught LanguageException: " << rt::getExceptionString(e) << endl; + assert_true(false); + } catch (const std::exception &e) { + cout << "Caught exception: " << e.what() << endl; + assert_true(false); + } + }); +} + +static void test_let_uaf_fixed(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + + // (let [x 1N] (+ "aa" x)) + // This used to cause UAF because both let and Numbers_add released x. + Node root; + root.set_op(opLet); + auto *let = root.mutable_subnode()->mutable_let(); + + auto *bx = let->add_bindings(); + bx->set_op(opBinding); + auto *bxc = bx->mutable_subnode()->mutable_binding(); + bxc->set_name("x"); + bxc->mutable_init()->set_op(opConst); + bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_val("1"); + bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + bxc->mutable_init()->set_tag("clojure.lang.BigInt"); + + auto *body = let->mutable_body(); + body->set_op(opStaticCall); + auto *sc = body->mutable_subnode()->mutable_staticcall(); + sc->set_class_("rt.Core"); + sc->set_method("Numbers_add"); + + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->mutable_subnode()->mutable_const_()->set_val("aa"); + arg1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); + + auto *arg2 = sc->add_args(); + arg2->set_op(opLocal); + arg2->mutable_subnode()->mutable_local()->set_name("x"); + arg2->mutable_subnode()->mutable_local()->set_local(localTypeLet); + + // Setup rt.Core/Numbers_add metadata + auto desc = std::make_unique(); + rt::IntrinsicDescription numAdd; + numAdd.type = rt::CallType::Call; + numAdd.symbol = "Numbers_add"; + numAdd.argTypes.push_back(rt::ObjectTypeSet::all()); + numAdd.argTypes.push_back(rt::ObjectTypeSet::all()); + numAdd.returnType = rt::ObjectTypeSet::all(); + desc->staticFns["Numbers_add"].push_back(numAdd); + + String *coreName = String_createDynamicStr("rt.Core"); + Ptr_retain(coreName); + ::Class *coreCls = Class_create(coreName, coreName, 0, nullptr); + coreCls->compilerExtension = desc.release(); + coreCls->compilerExtensionDestructor = rt::delete_class_description; + compState.classRegistry.registerObject("rt.Core", coreCls); + + cout << "=== Let UAF Fixed Test (Should throw LanguageException but NOT crash) ===" << endl; + try { + { + rt::JITEngine engine(compState); + auto res = engine.compileAST(root, "__test_let_uaf", llvm::OptimizationLevel::O0, true).get(); + res.toPtr()(); + assert_true(false); // Should have thrown + } + } catch (const rt::LanguageException &e) { + cout << "Caught expected LanguageException: " << rt::getExceptionString(e) << endl; + } + }); +} + +static void test_let_gap_leak(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + + // (let [x 1N] (do (+ "aa" "bb") x)) + Node root; + root.set_op(opLet); + auto *let = root.mutable_subnode()->mutable_let(); + + auto *bx = let->add_bindings(); + bx->set_op(opBinding); + auto *bxc = bx->mutable_subnode()->mutable_binding(); + bxc->set_name("x"); + bxc->mutable_init()->set_op(opConst); + bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_val("1"); + bxc->mutable_init()->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + bxc->mutable_init()->set_tag("clojure.lang.BigInt"); + + auto *body = let->mutable_body(); + body->set_op(opDo); + auto *do_ = body->mutable_subnode()->mutable_do_(); + + auto *stmt = do_->add_statements(); + stmt->set_op(opStaticCall); + auto *sc = stmt->mutable_subnode()->mutable_staticcall(); + sc->set_class_("rt.Core"); + sc->set_method("Numbers_add"); + auto *a1 = sc->add_args(); + a1->set_op(opConst); + a1->mutable_subnode()->mutable_const_()->set_val("aa"); + a1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); + auto *a2 = sc->add_args(); + a2->set_op(opConst); + a2->mutable_subnode()->mutable_const_()->set_val("bb"); + a2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); + + auto *ret = do_->mutable_ret(); + ret->set_op(opLocal); + ret->mutable_subnode()->mutable_local()->set_name("x"); + ret->mutable_subnode()->mutable_local()->set_local(localTypeLet); + + // Add unwind-memory guidance to simulate real compiler output + auto* g1 = body->add_unwindmemory(); + g1->set_variablename("x"); + g1->set_requiredrefcountchange(-1); + + auto* g2 = stmt->add_unwindmemory(); + g2->set_variablename("x"); + g2->set_requiredrefcountchange(-1); + + // Setup rt.Core/Numbers_add metadata + auto desc = std::make_unique(); + rt::IntrinsicDescription numAdd; + numAdd.type = rt::CallType::Call; + numAdd.symbol = "Numbers_add"; + numAdd.argTypes.push_back(rt::ObjectTypeSet::all()); + numAdd.argTypes.push_back(rt::ObjectTypeSet::all()); + numAdd.returnType = rt::ObjectTypeSet::all(); + desc->staticFns["Numbers_add"].push_back(numAdd); + + String *coreName = String_createDynamicStr("rt.Core"); + Ptr_retain(coreName); + ::Class *coreCls = Class_create(coreName, coreName, 0, nullptr); + coreCls->compilerExtension = desc.release(); + coreCls->compilerExtensionDestructor = rt::delete_class_description; + compState.classRegistry.registerObject("rt.Core", coreCls); + + cout << "=== Let Gap Leak Test (Should NOT leak) ===" << endl; + try { + { + rt::JITEngine engine(compState); + auto res = engine.compileAST(root, "__test_let_leak", llvm::OptimizationLevel::O0, true).get(); + res.toPtr()(); + assert_true(false); + } + } catch (const rt::LanguageException &e) { + cout << "Caught expected LanguageException: " << rt::getExceptionString(e) << endl; + } + }); +} + +static void test_let_nested_redundancy(void **state) { + (void)state; + // We can't use ASSERT_MEMORY_ALL_BALANCED because the JIT will retain the 100N constant + // but the inner let will release it once. This leads to a net balance of zero if we didn't account for constants. + // Actually, if we use separate JIT session per test, ASSERT_MEMORY_ALL_BALANCED(engine destruction) should work. - RTValue result = res.toPtr()(); - assert_true(RT_isPtr(result)); - String *s = (String *)RT_unboxPtr(result); - assert_string_equal(String_c_str(s), "hello"); - release(result); - } catch (const rt::LanguageException &e) { - cout << "Caught LanguageException: " << rt::getExceptionString(e) << endl; - assert_true(false); - } catch (const std::exception &e) { - cout << "Caught exception: " << e.what() << endl; - assert_true(false); - } + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + rt::JITEngine engine(compState); + + // (let [x 100N] + // (let [y 200N] 42)) + Node root; + root.set_op(opLet); + auto *outerLet = root.mutable_subnode()->mutable_let(); + + // Outer binding x = 100N + auto *bx1 = outerLet->add_bindings(); + bx1->set_op(opBinding); + auto *bxc1 = bx1->mutable_subnode()->mutable_binding(); + bxc1->set_name("x_outer"); + bxc1->mutable_init()->set_op(opConst); + bxc1->mutable_init()->mutable_subnode()->mutable_const_()->set_val("100"); + bxc1->mutable_init()->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + bxc1->mutable_init()->set_tag("clojure.lang.BigInt"); + + // Outer body: Inner Let + auto *innerNode = outerLet->mutable_body(); + innerNode->set_op(opLet); + auto *innerLet = innerNode->mutable_subnode()->mutable_let(); + + // Inner binding y = 200 + auto *bx2 = innerLet->add_bindings(); + bx2->set_op(opBinding); + auto *bxc2 = bx2->mutable_subnode()->mutable_binding(); + bxc2->set_name("y"); + bxc2->mutable_init()->set_op(opConst); + bxc2->mutable_init()->mutable_subnode()->mutable_const_()->set_val("200"); + bxc2->mutable_init()->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + bxc2->mutable_init()->set_tag("long"); + + // Guidance for the INNER let node: RELEASE x_outer. + auto *g = innerNode->add_dropmemory(); + g->set_variablename("x_outer"); + g->set_requiredrefcountchange(-1); + + // Body: 42 + auto *innerBody = innerLet->mutable_body(); + innerBody->set_op(opConst); + innerBody->mutable_subnode()->mutable_const_()->set_val("42"); + innerBody->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + + cout << "=== Let Nested Redundancy Detection Test ===" << endl; + try { + auto res = engine.compileAST(root, "__test_let_nested_redundancy", llvm::OptimizationLevel::O0, true).get(); + RTValue val = res.toPtr()(); + assert_int_equal(42, RT_unboxInt32(val)); + } catch (const std::exception &e) { + cout << "Caught exception: " << e.what() << endl; + assert_true(false); + } + }); } int main(void) { @@ -149,6 +379,9 @@ int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_let_basic), cmocka_unit_test(test_let_memory_mm), + cmocka_unit_test(test_let_uaf_fixed), + cmocka_unit_test(test_let_gap_leak), + cmocka_unit_test(test_let_nested_redundancy), }; int result = cmocka_run_group_tests(tests, NULL, NULL); diff --git a/backend-v2/tests/codegen/MathIntrinsicsRepro_test.cpp b/backend-v2/tests/codegen/MathIntrinsicsRepro_test.cpp new file mode 100644 index 00000000..2435cf1f --- /dev/null +++ b/backend-v2/tests/codegen/MathIntrinsicsRepro_test.cpp @@ -0,0 +1,135 @@ +#include "../../codegen/CodeGen.h" +#include "../../jit/JITEngine.h" +#include "../../runtime/RuntimeInterface.h" +#include "../../state/ThreadsafeCompilerState.h" +#include "../../tools/EdnParser.h" +#include "bytecode.pb.h" +#include +#include + +extern "C" { +#include "../../runtime/tests/TestTools.h" +#include +} + +using namespace std; +using namespace rt; +using namespace clojure::rt::protobuf::bytecode; + +static RTValue resPtrToValue(llvm::orc::ExecutorAddr res) { + return res.toPtr()(); +} + +static void setup_mock_math(rt::ThreadsafeCompilerState &compState) { + String *nameStr = String_create("Math"); + Ptr_retain(nameStr); + Class *cls = Class_create(nameStr, nameStr, 0, nullptr); + + ClassDescription *ext = new ClassDescription(); + ext->name = "Math"; + + // Register "pow" as an intrinsic that maps to Math_pow + IntrinsicDescription powIntrinsic; + powIntrinsic.symbol = "Math_pow"; + powIntrinsic.type = CallType::Intrinsic; + powIntrinsic.argTypes.push_back(ObjectTypeSet::dynamicType()); + powIntrinsic.argTypes.push_back(ObjectTypeSet::dynamicType()); + powIntrinsic.returnType = ObjectTypeSet(doubleType, false); + + ext->staticFns["pow"].push_back(powIntrinsic); + + cls->compilerExtension = ext; + cls->compilerExtensionDestructor = delete_class_description; + compState.classRegistry.registerObject("Math", cls); +} + +static void test_math_pow_uaf(void **state) { + (void)state; + + rt::ThreadsafeCompilerState compState; + setup_mock_math(compState); + JITEngine engine(compState); + + // (Math/pow "not-a-number" 5.0) + Node callNode; + callNode.set_op(opStaticCall); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("Math"); + sc->set_method("pow"); + + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); + arg1->mutable_subnode()->mutable_const_()->set_val("not-a-number"); + + auto *arg2 = sc->add_args(); + arg2->set_op(opConst); + arg2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeNumber); + arg2->mutable_subnode()->mutable_const_()->set_val("5.0"); + arg2->set_tag("double"); + + try { + std::cout << "Compiling UAF repro..." << std::endl; + auto resCall = engine.compileAST(callNode, "repro_uaf", llvm::OptimizationLevel::O0, true).get(); + std::cout << "Executing UAF repro..." << std::endl; + resPtrToValue(resCall); + fail_msg("Should have thrown LanguageException"); + } catch (const LanguageException &e) { + std::cout << "Caught expected LanguageException: " << e.what() << std::endl; + } catch (const std::exception &e) { + std::cout << "Caught expected std::exception: " << e.what() << std::endl; + } + + std::cout << "Invalidating module, this EXPECTED TO CRASH if UAF exists..." << std::endl; + engine.invalidate("repro_uaf"); + std::cout << "Invalidation finished (unexpectedly survived)" << std::endl; +} + +static void test_math_pow_leak(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + setup_mock_math(compState); + JITEngine engine(compState); + + // (Math/pow "not-a-number" "another-string") + Node callNode; + callNode.set_op(opStaticCall); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("Math"); + sc->set_method("pow"); + + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); + arg1->mutable_subnode()->mutable_const_()->set_val("not-a-number"); + + auto *arg2 = sc->add_args(); + arg2->set_op(opConst); + arg2->mutable_subnode()->mutable_const_()->set_type(ConstNode_ConstType_constTypeString); + arg2->mutable_subnode()->mutable_const_()->set_val("another-string"); + + try { + std::cout << "Compiling Leak repro..." << std::endl; + auto resCall = engine.compileAST(callNode, "repro_leak", llvm::OptimizationLevel::O0, true).get(); + std::cout << "Executing Leak repro..." << std::endl; + resPtrToValue(resCall); + } catch (const std::exception &e) { + std::cout << "Caught exception in Leak test: " << e.what() << std::endl; + } + + engine.invalidate("repro_leak"); + }); +} + +int main(void) { + initialise_memory(); + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_math_pow_uaf), + cmocka_unit_test(test_math_pow_leak), + }; + + int result = cmocka_run_group_tests(tests, NULL, NULL); + RuntimeInterface_cleanup(); + return result; +} diff --git a/backend-v2/tests/codegen/NoThrow_test.cpp b/backend-v2/tests/codegen/NoThrow_test.cpp index 298787fc..95d34e9b 100644 --- a/backend-v2/tests/codegen/NoThrow_test.cpp +++ b/backend-v2/tests/codegen/NoThrow_test.cpp @@ -80,7 +80,7 @@ static void test_hot_cold_separation(void **state) { d1->mutable_init()->mutable_subnode()->mutable_const_()->set_val("2"); cout << "=== Hot/Cold Separation Test IR ===" << endl; - auto res = engine.compileAST(root, "__hot_cold_test", llvm::OptimizationLevel::O0, true).get(); + [[maybe_unused]] auto res = engine.compileAST(root, "__hot_cold_test", llvm::OptimizationLevel::O0, true).get(); cout << "===================================" << endl; } @@ -143,7 +143,7 @@ static void test_crash_repro(void **state) { arg2->mutable_subnode()->mutable_var()->set_var("v/x"); cout << "=== Crash Repro Test IR ===" << endl; - auto res = engine.compileAST(root, "__crash_repro", llvm::OptimizationLevel::O0, true).get(); + [[maybe_unused]] auto res = engine.compileAST(root, "__crash_repro", llvm::OptimizationLevel::O0, true).get(); cout << "===========================" << endl; } @@ -167,7 +167,7 @@ static void test_whitelist_works(void **state) { // Compile and dump IR cout << "=== Whitelist Test IR ===" << endl; - auto res = engine.compileAST(root, "__whitelist_test", llvm::OptimizationLevel::O0, true).get(); + [[maybe_unused]] auto res = engine.compileAST(root, "__whitelist_test", llvm::OptimizationLevel::O0, true).get(); cout << "=========================" << endl; } diff --git a/backend-v2/tests/codegen/StaticCallNode_test.cpp b/backend-v2/tests/codegen/StaticCallNode_test.cpp index 6d608481..553070a1 100644 --- a/backend-v2/tests/codegen/StaticCallNode_test.cpp +++ b/backend-v2/tests/codegen/StaticCallNode_test.cpp @@ -57,8 +57,8 @@ static void setup_mock_runtime_full(rt::ThreadsafeCompilerState &compState) { IntrinsicDescription addII; addII.symbol = "mock_add_int"; addII.type = CallType::Call; - addII.argTypes = {ObjectTypeSet(integerType, false), - ObjectTypeSet(integerType, false)}; + addII.argTypes.push_back(ObjectTypeSet(integerType, false)); + addII.argTypes.push_back(ObjectTypeSet(integerType, false)); addII.returnType = ObjectTypeSet(integerType, false); ext->staticFns["add"].push_back(addII); @@ -66,8 +66,8 @@ static void setup_mock_runtime_full(rt::ThreadsafeCompilerState &compState) { IntrinsicDescription addDD; addDD.symbol = "mock_add_double"; addDD.type = CallType::Call; - addDD.argTypes = {ObjectTypeSet(doubleType, false), - ObjectTypeSet(doubleType, false)}; + addDD.argTypes.push_back(ObjectTypeSet(doubleType, false)); + addDD.argTypes.push_back(ObjectTypeSet(doubleType, false)); addDD.returnType = ObjectTypeSet(doubleType, false); ext->staticFns["add"].push_back(addDD); @@ -75,8 +75,8 @@ static void setup_mock_runtime_full(rt::ThreadsafeCompilerState &compState) { IntrinsicDescription addGen; addGen.symbol = "mock_add_generic"; addGen.type = CallType::Call; - addGen.argTypes = {ObjectTypeSet::dynamicType(), - ObjectTypeSet::dynamicType()}; + addGen.argTypes.push_back(ObjectTypeSet::dynamicType()); + addGen.argTypes.push_back(ObjectTypeSet::dynamicType()); addGen.returnType = ObjectTypeSet::dynamicType(); ext->staticFns["add"].push_back(addGen); @@ -89,8 +89,8 @@ static void setup_mock_runtime_full(rt::ThreadsafeCompilerState &compState) { IntrinsicDescription addBB; addBB.symbol = "mock_get_true"; // just a dummy addBB.type = CallType::Call; - addBB.argTypes = {ObjectTypeSet(booleanType, false), - ObjectTypeSet(booleanType, false)}; + addBB.argTypes.push_back(ObjectTypeSet(booleanType, false)); + addBB.argTypes.push_back(ObjectTypeSet(booleanType, false)); addBB.returnType = ObjectTypeSet(booleanType, false); ext->staticFns["ex_add"].push_back(addBB); @@ -99,8 +99,8 @@ static void setup_mock_runtime_full(rt::ThreadsafeCompilerState &compState) { IntrinsicDescription addComplex; addComplex.symbol = "Add"; addComplex.type = CallType::Intrinsic; - addComplex.argTypes = {ObjectTypeSet(integerType, false), - ObjectTypeSet(integerType, false)}; + addComplex.argTypes.push_back(ObjectTypeSet(integerType, false)); + addComplex.argTypes.push_back(ObjectTypeSet(integerType, false)); addComplex.returnType = ObjectTypeSet(integerType, false); ext->staticFns["complex_add"].push_back(addComplex); @@ -155,306 +155,320 @@ static void createIndeterminateArg(Node *node, const char *val, bool isDouble) { static void test_dynamic_dispatch_3tail(void **state) { (void)state; - rt::ThreadsafeCompilerState compState; - setup_mock_runtime_full(compState); - JITEngine engine(compState); - - // Test Case 1: Int + Int - { - Node callNode; - callNode.set_op(opStaticCall); - auto *sc = callNode.mutable_subnode()->mutable_staticcall(); - sc->set_class_("clojure.lang.Numbers"); - sc->set_method("add"); - createIndeterminateArg(sc->add_args(), "10", false); - createIndeterminateArg(sc->add_args(), "20", false); + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + setup_mock_runtime_full(compState); + JITEngine engine(compState); + + // Test Case 1: Int + Int + { + Node callNode; + callNode.set_op(opStaticCall); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("add"); + createIndeterminateArg(sc->add_args(), "10", false); + createIndeterminateArg(sc->add_args(), "20", false); + + [[maybe_unused]] auto resCall = engine + .compileAST(callNode, "__test_3tail_int", + llvm::OptimizationLevel::O0, true) + .get(); + RTValue result = resPtrToValue(resCall); + assert_true(RT_isInt32(result)); + assert_int_equal(1030, RT_unboxInt32(result)); + release(result); + } + + // Test Case 2: Double + Double + { + Node callNode; + callNode.set_op(opStaticCall); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("add"); + createIndeterminateArg(sc->add_args(), "10.5", true); + createIndeterminateArg(sc->add_args(), "20.5", true); + + auto resCallD = engine + .compileAST(callNode, "__test_3tail_double", + llvm::OptimizationLevel::O0, true) + .get(); + RTValue resultD = resPtrToValue(resCallD); + assert_true(RT_isDouble(resultD)); + assert_double_equal(2031.0, RT_unboxDouble(resultD), 0.001); + release(resultD); + } + }); +} - auto resCall = engine - .compileAST(callNode, "__test_3tail_int", - llvm::OptimizationLevel::O0, true) - .get(); - RTValue result = resPtrToValue(resCall); - assert_true(RT_isInt32(result)); - assert_int_equal(1030, RT_unboxInt32(result)); - release(result); - } +static void test_dynamic_dispatch_filtering(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + setup_mock_runtime_full(compState); + JITEngine engine(compState); - // Test Case 2: Double + Double - { Node callNode; callNode.set_op(opStaticCall); auto *sc = callNode.mutable_subnode()->mutable_staticcall(); sc->set_class_("clojure.lang.Numbers"); sc->set_method("add"); - createIndeterminateArg(sc->add_args(), "10.5", true); + + // Arg 1: Statically Double + { + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + arg1->mutable_subnode()->mutable_const_()->set_val("10.5"); + arg1->set_tag("double"); + } + + // Arg 2: Statically Any (using If) createIndeterminateArg(sc->add_args(), "20.5", true); - auto resCallD = engine - .compileAST(callNode, "__test_3tail_double", - llvm::OptimizationLevel::O0, true) - .get(); - RTValue resultD = resPtrToValue(resCallD); - assert_true(RT_isDouble(resultD)); - assert_double_equal(2031.0, RT_unboxDouble(resultD), 0.001); - release(resultD); - } + try { + auto resCall = engine + .compileAST(callNode, "__test_filtering", + llvm::OptimizationLevel::O0, true) + .get(); + RTValue result = resPtrToValue(resCall); + assert_true(RT_isDouble(result)); + assert_double_equal(10.5 + 20.5 + 2000.0, RT_unboxDouble(result), 0.001); + release(result); + } catch (const std::exception &e) { + fprintf(stderr, "Exception: %s\n", e.what()); + assert_true(false); + } + }); } -static void test_dynamic_dispatch_filtering(void **state) { +static void test_dynamic_dispatch_exhaustive(void **state) { (void)state; - rt::ThreadsafeCompilerState compState; - setup_mock_runtime_full(compState); - JITEngine engine(compState); + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + setup_mock_runtime_full(compState); + JITEngine engine(compState); - Node callNode; - callNode.set_op(opStaticCall); - auto *sc = callNode.mutable_subnode()->mutable_staticcall(); - sc->set_class_("clojure.lang.Numbers"); - sc->set_method("add"); + // Call "ex_add" (no generic, 3 specializations) + // Args: statically Any (using If) + Node callNode; + callNode.set_op(opStaticCall); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("ex_add"); - // Arg 1: Statically Double - { - auto *arg1 = sc->add_args(); - arg1->set_op(opConst); - arg1->mutable_subnode()->mutable_const_()->set_type( - ConstNode_ConstType_constTypeNumber); - arg1->mutable_subnode()->mutable_const_()->set_val("10.5"); - arg1->set_tag("double"); - } + createIndeterminateArg(sc->add_args(), "10", false); + createIndeterminateArg(sc->add_args(), "20", false); - // Arg 2: Statically Any (using If) - createIndeterminateArg(sc->add_args(), "20.5", true); - - try { - auto resCall = engine - .compileAST(callNode, "__test_filtering", - llvm::OptimizationLevel::O0, true) - .get(); - RTValue result = resPtrToValue(resCall); - assert_true(RT_isDouble(result)); - assert_double_equal(10.5 + 20.5 + 2000.0, RT_unboxDouble(result), 0.001); - release(result); - } catch (const std::exception &e) { - fprintf(stderr, "Exception: %s\n", e.what()); - assert_true(false); - } -} - -static void test_dynamic_dispatch_exhaustive(void **state) { - (void)state; - rt::ThreadsafeCompilerState compState; - setup_mock_runtime_full(compState); - JITEngine engine(compState); - - // Call "ex_add" (no generic, 3 specializations) - // Args: statically Any (using If) - Node callNode; - callNode.set_op(opStaticCall); - auto *sc = callNode.mutable_subnode()->mutable_staticcall(); - sc->set_class_("clojure.lang.Numbers"); - sc->set_method("ex_add"); - - createIndeterminateArg(sc->add_args(), "10", false); - createIndeterminateArg(sc->add_args(), "20", false); - - try { - auto resCall = engine - .compileAST(callNode, "__test_exhaustive", - llvm::OptimizationLevel::O0, true) - .get(); - RTValue result = resPtrToValue(resCall); - assert_true(RT_isInt32(result)); - assert_int_equal(1030, RT_unboxInt32(result)); - release(result); - } catch (const std::exception &e) { - fprintf(stderr, "Exception: %s\n", e.what()); - assert_true(false); - } + try { + [[maybe_unused]] auto resCall = engine + .compileAST(callNode, "__test_exhaustive", + llvm::OptimizationLevel::O0, true) + .get(); + RTValue result = resPtrToValue(resCall); + assert_true(RT_isInt32(result)); + assert_int_equal(1030, RT_unboxInt32(result)); + release(result); + } catch (const std::exception &e) { + fprintf(stderr, "Exception: %s\n", e.what()); + assert_true(false); + } + }); } static void test_dynamic_dispatch_no_match(void **state) { (void)state; - rt::ThreadsafeCompilerState compState; - setup_mock_runtime_full(compState); - JITEngine engine(compState); - - // Call "ex_add" but with (String, String) -> Should throw runtime exception - Node callNode; - callNode.set_op(opStaticCall); - auto *sc = callNode.mutable_subnode()->mutable_staticcall(); - sc->set_class_("clojure.lang.Numbers"); - sc->set_method("ex_add"); - - // Arg 1: String - { - auto *arg1 = sc->add_args(); - arg1->set_op(opConst); - arg1->mutable_subnode()->mutable_const_()->set_type( - ConstNode_ConstType_constTypeString); - arg1->mutable_subnode()->mutable_const_()->set_val("hello"); - } - // Arg 2: String - { - auto *arg2 = sc->add_args(); - arg2->set_op(opConst); - arg2->mutable_subnode()->mutable_const_()->set_type( - ConstNode_ConstType_constTypeString); - arg2->mutable_subnode()->mutable_const_()->set_val("world"); - } + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + setup_mock_runtime_full(compState); + JITEngine engine(compState); - try { - auto resCall = engine - .compileAST(callNode, "__test_no_match", - llvm::OptimizationLevel::O0, true) - .get(); - // Since both are strings, it's statically possible they match a dynamic - // overload if it existed, but we filtered versions. (String, String) is NOT - // statically possible for any of (int, int), (double, double), (bool, - // bool). Wait, if it's NOT statically possible, codegen should throw - // compile-time error! - assert_true(false); // Should not reach here - } catch (const LanguageException &e) { - // Correctly threw compile-time exception because no statically possible - // overloads exist - assert_true(true); - } catch (const std::exception &e) { - fprintf(stderr, "Unexpected exception: %s\n", e.what()); - assert_true(false); - } + // Call "ex_add" but with (String, String) -> Should throw runtime exception + Node callNode; + callNode.set_op(opStaticCall); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("ex_add"); + + // Arg 1: String + { + auto *arg1 = sc->add_args(); + arg1->set_op(opConst); + arg1->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeString); + arg1->mutable_subnode()->mutable_const_()->set_val("hello"); + } + // Arg 2: String + { + auto *arg2 = sc->add_args(); + arg2->set_op(opConst); + arg2->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeString); + arg2->mutable_subnode()->mutable_const_()->set_val("world"); + } + + try { + [[maybe_unused]] auto resCall = engine + .compileAST(callNode, "__test_no_match", + llvm::OptimizationLevel::O0, true) + .get(); + // Since both are strings, it's statically possible they match a dynamic + // overload if it existed, but we filtered versions. (String, String) is NOT + // statically possible for any of (int, int), (double, double), (bool, + // bool). Wait, if it's NOT statically possible, codegen should throw + // compile-time error! + assert_true(false); // Should not reach here + } catch (const LanguageException &e) { + // Correctly threw compile-time exception because no statically possible + // overloads exist + assert_true(true); + } catch (const std::exception &e) { + fprintf(stderr, "Unexpected exception: %s\n", e.what()); + assert_true(false); + } + }); } static void test_dynamic_dispatch_no_match_runtime(void **state) { (void)state; - rt::ThreadsafeCompilerState compState; - setup_mock_runtime_full(compState); - JITEngine engine(compState); - - // To test RUNTIME exception, we need arguments that are statically ANY but - // runtime NOT matching any specialization. - Node callNode; - callNode.set_op(opStaticCall); - auto *sc = callNode.mutable_subnode()->mutable_staticcall(); - sc->set_class_("clojure.lang.Numbers"); - sc->set_method("ex_add"); - - // Helper method to create a truly generic "String" at runtime - auto createGenericString = [&](Node *node) { - node->set_op(opIf); - auto *if_ = node->mutable_subnode()->mutable_if_(); - auto *test = if_->mutable_test(); - test->set_op(opStaticCall); - test->mutable_subnode()->mutable_staticcall()->set_class_("Helper"); - test->mutable_subnode()->mutable_staticcall()->set_method("getTrue"); - - auto *then = if_->mutable_then(); - then->set_op(opConst); - then->mutable_subnode()->mutable_const_()->set_type( - ConstNode_ConstType_constTypeString); - then->mutable_subnode()->mutable_const_()->set_val("mismatch"); - - auto *else_ = if_->mutable_else_(); - else_->set_op(opConst); - else_->mutable_subnode()->mutable_const_()->set_type( - ConstNode_ConstType_constTypeNumber); - else_->mutable_subnode()->mutable_const_()->set_val("123"); - else_->set_tag("long"); - }; + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + setup_mock_runtime_full(compState); + JITEngine engine(compState); - createGenericString(sc->add_args()); - createGenericString(sc->add_args()); - - try { - auto resCall = engine - .compileAST(callNode, "__test_no_match_runtime", - llvm::OptimizationLevel::O0, true) - .get(); - resPtrToValue(resCall); - assert_true(false); // Should have thrown runtime exception - } catch (const LanguageException &e) { - // Exception: NoMatchingOverloadException - // Message: No matching overload found for clojure.lang.Numbers/ex_add - assert_true(true); - } catch (const std::exception &e) { - fprintf(stderr, "Unexpected exception: %s\n", e.what()); - assert_true(false); - } + // To test RUNTIME exception, we need arguments that are statically ANY but + // runtime NOT matching any specialization. + Node callNode; + callNode.set_op(opStaticCall); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("ex_add"); + + // Helper method to create a truly generic "String" at runtime + auto createGenericString = [&](Node *node) { + node->set_op(opIf); + auto *if_ = node->mutable_subnode()->mutable_if_(); + auto *test = if_->mutable_test(); + test->set_op(opStaticCall); + test->mutable_subnode()->mutable_staticcall()->set_class_("Helper"); + test->mutable_subnode()->mutable_staticcall()->set_method("getTrue"); + + auto *then = if_->mutable_then(); + then->set_op(opConst); + then->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeString); + then->mutable_subnode()->mutable_const_()->set_val("mismatch"); + + auto *else_ = if_->mutable_else_(); + else_->set_op(opConst); + else_->mutable_subnode()->mutable_const_()->set_type( + ConstNode_ConstType_constTypeNumber); + else_->mutable_subnode()->mutable_const_()->set_val("123"); + else_->set_tag("long"); + }; + + createGenericString(sc->add_args()); + createGenericString(sc->add_args()); + + try { + auto resCall = engine + .compileAST(callNode, "__test_no_match_runtime", + llvm::OptimizationLevel::O0, true) + .get(); + resPtrToValue(resCall); + assert_true(false); // Should have thrown runtime exception + } catch (const LanguageException &e) { + // Exception: NoMatchingOverloadException + // Message: No matching overload found for clojure.lang.Numbers/ex_add + assert_true(true); + } catch (const std::exception &e) { + fprintf(stderr, "Unexpected exception: %s\n", e.what()); + assert_true(false); + } + }); } static void test_regression_type_narrowing_specialized(void **state) { (void)state; - rt::ThreadsafeCompilerState compState; - setup_mock_runtime_full(compState); - JITEngine engine(compState); - - // This test specifically documents the fix for the - // "InternalInconsistencyException" caused by type mismatch in specialized - // branches. We call "add" with arguments that are statically indeterminate - // (Any). The dispatch logic will: - // 1. Check if they are (int, int) at runtime. - // 2. Unbox them if true. - // 3. Call mock_add_int(i32, i32). - // The InvokeManager::generateIntrinsic checks if the arguments passed to - // mock_add_int match the IntrinsicDescription. If we don't narrow the type of - // the unboxed TypedValue, it will still be "Any" and InvokeManager will throw - // an exception. - Node callNode; - callNode.set_op(opStaticCall); - auto *sc = callNode.mutable_subnode()->mutable_staticcall(); - sc->set_class_("clojure.lang.Numbers"); - sc->set_method("add"); - - createIndeterminateArg(sc->add_args(), "42", false); - createIndeterminateArg(sc->add_args(), "8", false); - - try { - auto resCall = engine - .compileAST(callNode, "__test_regression_narrowing", - llvm::OptimizationLevel::O0, true) - .get(); - RTValue result = resPtrToValue(resCall); - assert_true(RT_isInt32(result)); - assert_int_equal(1050, RT_unboxInt32(result)); // mock_add_int adds 1000 - release(result); - } catch (const std::exception &e) { - fprintf(stderr, "Regression failure: %s\n", e.what()); - assert_true(false); - } + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + setup_mock_runtime_full(compState); + JITEngine engine(compState); + + // This test specifically documents the fix for the + // "InternalInconsistencyException" caused by type mismatch in specialized + // branches. We call "add" with arguments that are statically indeterminate + // (Any). The dispatch logic will: + // 1. Check if they are (int, int) at runtime. + // 2. Unbox them if true. + // 3. Call mock_add_int(i32, i32). + // The InvokeManager::generateIntrinsic checks if the arguments passed to + // mock_add_int match the IntrinsicDescription. If we don't narrow the type of + // the unboxed TypedValue, it will still be "Any" and InvokeManager will throw + // an exception. + Node callNode; + callNode.set_op(opStaticCall); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("add"); + + createIndeterminateArg(sc->add_args(), "42", false); + createIndeterminateArg(sc->add_args(), "8", false); + + try { + auto resCall = engine + .compileAST(callNode, "__test_regression_narrowing", + llvm::OptimizationLevel::O0, true) + .get(); + RTValue result = resPtrToValue(resCall); + assert_true(RT_isInt32(result)); + assert_int_equal(1050, RT_unboxInt32(result)); // mock_add_int adds 1000 + release(result); + } catch (const std::exception &e) { + fprintf(stderr, "Regression failure: %s\n", e.what()); + assert_true(false); + } + }); } static void test_regression_phi_node_segfault(void **state) { (void)state; - rt::ThreadsafeCompilerState compState; - setup_mock_runtime_full(compState); - JITEngine engine(compState); - - // This test specifically documents the fix for the LLVM segfault (SimplifyCFG - // crash) caused by malformed PHI nodes when a specialized branch contains - // internal branching. "complex_add" specialization "Add" (intrinsic) creates - // "overflow" and "no_overflow" blocks. - Node callNode; - callNode.set_op(opStaticCall); - auto *sc = callNode.mutable_subnode()->mutable_staticcall(); - sc->set_class_("clojure.lang.Numbers"); - sc->set_method("complex_add"); - - createIndeterminateArg(sc->add_args(), "100", false); - createIndeterminateArg(sc->add_args(), "200", false); - - try { - // Optimization level O1 or higher is needed to trigger SimplifyCFG in a way - // that crashes - auto resCall = engine - .compileAST(callNode, "__test_regression_segfault", - llvm::OptimizationLevel::O1, true) - .get(); - RTValue result = resPtrToValue(resCall); - assert_true(RT_isInt32(result)); - assert_int_equal(300, RT_unboxInt32(result)); // Real Add doesn't add 1000 - release(result); - } catch (const std::exception &e) { - fprintf(stderr, "Segfault regression failure: %s\n", e.what()); - assert_true(false); - } + ASSERT_MEMORY_ALL_BALANCED({ + rt::ThreadsafeCompilerState compState; + setup_mock_runtime_full(compState); + JITEngine engine(compState); + + // This test specifically documents the fix for the LLVM segfault (SimplifyCFG + // crash) caused by malformed PHI nodes when a specialized branch contains + // internal branching. "complex_add" specialization "Add" (intrinsic) creates + // "overflow" and "no_overflow" blocks. + Node callNode; + callNode.set_op(opStaticCall); + auto *sc = callNode.mutable_subnode()->mutable_staticcall(); + sc->set_class_("clojure.lang.Numbers"); + sc->set_method("complex_add"); + + createIndeterminateArg(sc->add_args(), "100", false); + createIndeterminateArg(sc->add_args(), "200", false); + + try { + // Optimization level O1 or higher is needed to trigger SimplifyCFG in a way + // that crashes + auto resCall = engine + .compileAST(callNode, "__test_regression_segfault", + llvm::OptimizationLevel::O1, true) + .get(); + RTValue result = resPtrToValue(resCall); + assert_true(RT_isInt32(result)); + assert_int_equal(300, RT_unboxInt32(result)); // Real Add doesn't add 1000 + release(result); + } catch (const std::exception &e) { + fprintf(stderr, "Segfault regression failure: %s\n", e.what()); + assert_true(false); + } + }); } int main(void) { diff --git a/frontend/resources/rt-classes.edn b/frontend/resources/rt-classes.edn index 85aeed97..a3c95893 100644 --- a/frontend/resources/rt-classes.edn +++ b/frontend/resources/rt-classes.edn @@ -192,102 +192,77 @@ java.lang.Math {:static-fns - {sin [{:args [:double] :type :intrinsic :symbol "Math_sin" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_sin" :returns :double} + {sin [{:args [:double] :type :intrinsic :symbol "Math_sin_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_sin_I" :returns :double} {:args [:bigint] :type :intrinsic :symbol "Math_sin" :returns :double} {:args [:ratio] :type :intrinsic :symbol "Math_sin" :returns :double} {:args [:any] :type :intrinsic :symbol "Math_sin" :returns :double}] - cos [{:args [:double] :type :intrinsic :symbol "Math_cos" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_cos" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_cos" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_cos" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_cos" :returns :double}] - tan [{:args [:double] :type :intrinsic :symbol "Math_tan" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_tan" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_tan" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_tan" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_tan" :returns :double}] - asin [{:args [:double] :type :intrinsic :symbol "Math_asin" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_asin" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_asin" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_asin" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_asin" :returns :double}] - acos [{:args [:double] :type :intrinsic :symbol "Math_acos" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_acos" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_acos" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_acos" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_acos" :returns :double}] - atan [{:args [:double] :type :intrinsic :symbol "Math_atan" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_atan" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_atan" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_atan" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_atan" :returns :double}] - exp [{:args [:double] :type :intrinsic :symbol "Math_exp" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_exp" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_exp" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_exp" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_exp" :returns :double}] - exp2 [{:args [:double] :type :intrinsic :symbol "Math_exp2" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_exp2" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_exp2" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_exp2" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_exp2" :returns :double}] - exp10 [{:args [:double] :type :intrinsic :symbol "Math_exp10" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_exp10" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_exp10" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_exp10" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_exp10" :returns :double}] - log [{:args [:double] :type :intrinsic :symbol "Math_log" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_log" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_log" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_log" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_log" :returns :double}] - log10 [{:args [:double] :type :intrinsic :symbol "Math_log10" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_log10" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_log10" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_log10" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_log10" :returns :double}] - logb [{:args [:double] :type :intrinsic :symbol "Math_logb" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_logb" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_logb" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_logb" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_logb" :returns :double}] - log2 [{:args [:double] :type :intrinsic :symbol "Math_log2" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_log2" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_log2" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_log2" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_log2" :returns :double}] - sqrt [{:args [:double] :type :intrinsic :symbol "Math_sqrt" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_sqrt" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_sqrt" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_sqrt" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_sqrt" :returns :double}] - cbrt [{:args [:double] :type :intrinsic :symbol "Math_cbrt" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_cbrt" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_cbrt" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_cbrt" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_cbrt" :returns :double}] - exp1m [{:args [:double] :type :intrinsic :symbol "Math_exp1m" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_exp1m" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_exp1m" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_exp1m" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_exp1m" :returns :double}] - log1p [{:args [:double] :type :intrinsic :symbol "Math_log1p" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_log1p" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_log1p" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_log1p" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_log1p" :returns :double}] - abs [{:args [:double] :type :intrinsic :symbol "Math_abs" :returns :double} - {:args [:int] :type :intrinsic :symbol "Math_abs" :returns :double} - {:args [:bigint] :type :intrinsic :symbol "Math_abs" :returns :double} - {:args [:ratio] :type :intrinsic :symbol "Math_abs" :returns :double} - {:args [:any] :type :intrinsic :symbol "Math_abs" :returns :double}] - atan2 [{:args [:double :double] :type :intrinsic :symbol "Math_atan2" :returns :double} - {:args [:any :any] :type :intrinsic :symbol "Math_atan2" :returns :double}] - pow [{:args [:double :double] :type :intrinsic :symbol "Math_pow" :returns :double} - {:args [:any :any] :type :intrinsic :symbol "Math_pow" :returns :double}] - hypot [{:args [:double :double] :type :intrinsic :symbol "Math_hypot" :returns :double} - {:args [:any :any] :type :intrinsic :symbol "Math_hypot" :returns :double}]}} + cos [{:args [:double] :type :intrinsic :symbol "Math_cos_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_cos_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_cos" :returns :double}] + tan [{:args [:double] :type :intrinsic :symbol "Math_tan_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_tan_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_tan" :returns :double}] + asin [{:args [:double] :type :intrinsic :symbol "Math_asin_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_asin_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_asin" :returns :double}] + acos [{:args [:double] :type :intrinsic :symbol "Math_acos_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_acos_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_acos" :returns :double}] + atan [{:args [:double] :type :intrinsic :symbol "Math_atan_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_atan_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_atan" :returns :double}] + exp [{:args [:double] :type :intrinsic :symbol "Math_exp_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_exp_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_exp" :returns :double}] + exp2 [{:args [:double] :type :intrinsic :symbol "Math_exp2_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_exp2_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_exp2" :returns :double}] + exp10 [{:args [:double] :type :intrinsic :symbol "Math_exp10_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_exp10_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_exp10" :returns :double}] + log [{:args [:double] :type :intrinsic :symbol "Math_log_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_log_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_log" :returns :double}] + log10 [{:args [:double] :type :intrinsic :symbol "Math_log10_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_log10_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_log10" :returns :double}] + logb [{:args [:double] :type :intrinsic :symbol "Math_logb_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_logb_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_logb" :returns :double}] + log2 [{:args [:double] :type :intrinsic :symbol "Math_log2_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_log2_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_log2" :returns :double}] + sqrt [{:args [:double] :type :intrinsic :symbol "Math_sqrt_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_sqrt_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_sqrt" :returns :double}] + cbrt [{:args [:double] :type :intrinsic :symbol "Math_cbrt_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_cbrt_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_cbrt" :returns :double}] + exp1m [{:args [:double] :type :intrinsic :symbol "Math_exp1m_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_exp1m_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_exp1m" :returns :double}] + log1p [{:args [:double] :type :intrinsic :symbol "Math_log1p_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_log1p_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_log1p" :returns :double}] + abs [{:args [:double] :type :intrinsic :symbol "Math_abs_D" :returns :double} + {:args [:int] :type :intrinsic :symbol "Math_abs_I" :returns :double} + {:args [:any] :type :intrinsic :symbol "Math_abs" :returns :double}] + atan2 [{:args [:double :double] :type :intrinsic :symbol "Math_atan2_DD" :returns :double} + {:args [:int :int] :type :intrinsic :symbol "Math_atan2_II" :returns :double} + {:args [:int :double] :type :intrinsic :symbol "Math_atan2_ID" :returns :double} + {:args [:double :int] :type :intrinsic :symbol "Math_atan2_DI" :returns :double} + {:args [:any :any] :type :intrinsic :symbol "Math_atan2" :returns :double}] + pow [{:args [:double :double] :type :intrinsic :symbol "Math_pow_DD" :returns :double} + {:args [:int :int] :type :intrinsic :symbol "Math_pow_II" :returns :double} + {:args [:int :double] :type :intrinsic :symbol "Math_pow_ID" :returns :double} + {:args [:double :int] :type :intrinsic :symbol "Math_pow_DI" :returns :double} + {:args [:any :any] :type :intrinsic :symbol "Math_pow" :returns :double}] + hypot [{:args [:double :double] :type :intrinsic :symbol "Math_hypot_DD" :returns :double} + {:args [:int :int] :type :intrinsic :symbol "Math_hypot_II" :returns :double} + {:args [:int :double] :type :intrinsic :symbol "Math_hypot_ID" :returns :double} + {:args [:double :int] :type :intrinsic :symbol "Math_hypot_DI" :returns :double} + {:args [:any :any] :type :intrinsic :symbol "Math_hypot" :returns :double}]}} java.lang.String {:object-type 7