diff --git a/backend-v2/CMakeLists.txt b/backend-v2/CMakeLists.txt index e4013d11..14ab829f 100644 --- a/backend-v2/CMakeLists.txt +++ b/backend-v2/CMakeLists.txt @@ -128,7 +128,10 @@ set(BACKEND_SOURCES codegen/ops/VectorNode.cpp codegen/ops/MapNode.cpp codegen/ops/StaticCallNode.cpp - codegen/ops/IfNode.cpp) + codegen/ops/IfNode.cpp + codegen/ops/DefNode.cpp + codegen/ops/TheVarNode.cpp + codegen/ops/VarNode.cpp) # 2. Protobuf Detection diff --git a/backend-v2/codegen/CodeGen.cpp b/backend-v2/codegen/CodeGen.cpp index 4848ac15..f07e7df9 100644 --- a/backend-v2/codegen/CodeGen.cpp +++ b/backend-v2/codegen/CodeGen.cpp @@ -2,14 +2,25 @@ #include "../bridge/Exceptions.h" #include "../cljassert.h" #include "TypedValue.h" +#include "runtime/RTValue.h" +#include "runtime/String.h" +#include "runtime/Object.h" using namespace llvm; namespace rt { + +CodeGen::~CodeGen() { + for (auto &val : generatedConstants) { + Ptr_release(RT_unboxPtr(val)); + } +} + CodeGenResult CodeGen::release() && { DIB->finalize(); - return {std::move(TSContext), std::move(TheModule), - std::move(generatedConstants)}; + auto constants = std::move(generatedConstants); + generatedConstants.clear(); // Ensure destructor doesn't re-release + return {std::move(TSContext), std::move(TheModule), std::move(constants)}; } std::string CodeGen::codegenTopLevel(const Node &node) { @@ -99,8 +110,8 @@ TypedValue CodeGen::codegen(const Node &node, // return codegen(node, node.subnode().casethen(), typeRestrictions); // case opCatch: // return codegen(node, node.subnode().catch_(), typeRestrictions); - // case opDef: - // return codegen(node, node.subnode().def(), typeRestrictions); + case opDef: + return codegen(node, node.subnode().def(), typeRestrictions); // case opDeftype: // return codegen(node, node.subnode().deftype(), typeRestrictions); // case opDo: @@ -155,14 +166,14 @@ TypedValue CodeGen::codegen(const Node &node, // return codegen(node, node.subnode().mutateset(), typeRestrictions); // case opStaticField: // return codegen(node, node.subnode().staticfield(), typeRestrictions); - // case opTheVar: - // return codegen(node, node.subnode().thevar(), typeRestrictions); + case opTheVar: + return codegen(node, node.subnode().thevar(), typeRestrictions); // case opThrow: // return codegen(node, node.subnode().throw_(), typeRestrictions); // case opTry: // return codegen(node, node.subnode().try_(), typeRestrictions); - // case opVar: - // return codegen(node, node.subnode().var(), typeRestrictions); + case opVar: + return codegen(node, node.subnode().var(), typeRestrictions); // case opWithMeta: // return codegen(node, node.subnode().withmeta(), typeRestrictions); default: @@ -199,8 +210,8 @@ ObjectTypeSet CodeGen::getType(const Node &node, // return getType(node, node.subnode().casethen(), typeRestrictions); // case opCatch: // return getType(node, node.subnode().catch_(), typeRestrictions); - // case opDef: - // return getType(node, node.subnode().def(), typeRestrictions); + case opDef: + return getType(node, node.subnode().def(), typeRestrictions); // case opDeftype: // return getType(node, node.subnode().deftype(), typeRestrictions); // case opDo: @@ -255,14 +266,14 @@ ObjectTypeSet CodeGen::getType(const Node &node, // return getType(node, node.subnode().mutateset(), typeRestrictions); // case opStaticField: // return getType(node, node.subnode().staticfield(), typeRestrictions); - // case opTheVar: - // return getType(node, node.subnode().thevar(), typeRestrictions); + case opTheVar: + return getType(node, node.subnode().thevar(), typeRestrictions); // case opThrow: // return getType(node, node.subnode().throw_(), typeRestrictions); // case opTry: // return getType(node, node.subnode().try_(), typeRestrictions); - // case opVar: - // return getType(node, node.subnode().var(), typeRestrictions); + case opVar: + return getType(node, node.subnode().var(), typeRestrictions); // case opWithMeta: // return getType(node, node.subnode().withmeta(), typeRestrictions); default: @@ -274,5 +285,12 @@ ObjectTypeSet CodeGen::getType(const Node &node, return ObjectTypeSet::all(); } +Var *CodeGen::getOrCreateVar(std::string_view name) { + return compilerState.varRegistry.getOrCreate( + std::string(name).c_str(), [this, name]() { + auto n = std::string(name); + return dynamicConstructor.createVarRaw(n.c_str()); + }); +} } // namespace rt diff --git a/backend-v2/codegen/CodeGen.h b/backend-v2/codegen/CodeGen.h index fcb15be7..5b7c9cd8 100644 --- a/backend-v2/codegen/CodeGen.h +++ b/backend-v2/codegen/CodeGen.h @@ -32,15 +32,6 @@ struct CodeGenResult { std::vector constants; }; -class CodeGenerationException : public std::runtime_error { - const Node *node; - -public: - CodeGenerationException(const std::string &msg, const Node &n) - : std::runtime_error(msg), node(&n) {} - const Node &getNode() const { return *node; } -}; - class CodeGen { std::unique_ptr TSContext; @@ -88,6 +79,8 @@ class CodeGen { false, "", 0); } + ~CodeGen(); + CodeGenResult release() &&; std::string codegenTopLevel(const Node &node); @@ -104,16 +97,22 @@ class CodeGen { const ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, const StaticCallNode &subnode, const ObjectTypeSet &typeRestrictions); - + TypedValue codegen(const Node &node, const TheVarNode &subnode, + const ObjectTypeSet &typeRestrictions); TypedValue codegen(const Node &node, const IfNode &subnode, const ObjectTypeSet &typeRestrictions); - ObjectTypeSet getType(const Node &node, const IfNode &subnode, - const ObjectTypeSet &typeRestrictions); - + TypedValue codegen(const Node &node, const VarNode &subnode, + const ObjectTypeSet &typeRestrictions); + TypedValue codegen(const Node &node, const DefNode &subnode, + const ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, const ObjectTypeSet &typeRestrictions); + ObjectTypeSet getType(const Node &node, const TheVarNode &subnode, + const ObjectTypeSet &typeRestrictions); + ObjectTypeSet getType(const Node &node, const IfNode &subnode, + const ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, const ConstNode &subnode, const ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, const QuoteNode &subnode, @@ -124,6 +123,11 @@ class CodeGen { const ObjectTypeSet &typeRestrictions); ObjectTypeSet getType(const Node &node, const StaticCallNode &subnode, const ObjectTypeSet &typeRestrictions); + ObjectTypeSet getType(const Node &node, const VarNode &subnode, + const ObjectTypeSet &typeRestrictions); + ObjectTypeSet getType(const Node &node, const DefNode &subnode, + const ObjectTypeSet &typeRestrictions); + Var *getOrCreateVar(std::string_view name); }; } // namespace rt diff --git a/backend-v2/codegen/DynamicConstructor.cpp b/backend-v2/codegen/DynamicConstructor.cpp index 47341ecb..e554cf8d 100644 --- a/backend-v2/codegen/DynamicConstructor.cpp +++ b/backend-v2/codegen/DynamicConstructor.cpp @@ -7,6 +7,9 @@ #include "TypedValue.h" #include "ValueEncoder.h" #include "invoke/InvokeManager.h" +#include "runtime/Keyword.h" +#include "runtime/RTValue.h" +#include "runtime/Var.h" #include "llvm/ADT/StringSwitch.h" #include #include @@ -71,6 +74,19 @@ TypedValue DynamicConstructor::createString(const char *s) { types.ptrTy)); } +TypedValue DynamicConstructor::createVar(const char *name) { + Var *var = Var_create(Keyword_create(String_createDynamicStr(name))); + uintptr_t address = reinterpret_cast(var); + return TypedValue(ObjectTypeSet(varType), + ConstantExpr::getIntToPtr( + ConstantInt::get(types.i64Ty, address), types.ptrTy)); +} + +Var *DynamicConstructor::createVarRaw(const char *name) { + Var *var = Var_create(Keyword_create(String_createDynamicStr(name))); + return var; +} + TypedValue DynamicConstructor::createKeyword(const char *s) { String *str = String_createDynamicStr(s); RTValue kwVal = Keyword_create(str); diff --git a/backend-v2/codegen/DynamicConstructor.h b/backend-v2/codegen/DynamicConstructor.h index 55d19a76..5ee61829 100644 --- a/backend-v2/codegen/DynamicConstructor.h +++ b/backend-v2/codegen/DynamicConstructor.h @@ -39,6 +39,8 @@ class DynamicConstructor { TypedValue createSymbol(const char *s); TypedValue createBigInteger(const char *s); TypedValue createRatio(const char *s); + TypedValue createVar(const char *name); + Var *createVarRaw(const char *name); /* * TODO: diff --git a/backend-v2/codegen/ops/ConstNode.cpp b/backend-v2/codegen/ops/ConstNode.cpp index 73e74e63..d1e37188 100644 --- a/backend-v2/codegen/ops/ConstNode.cpp +++ b/backend-v2/codegen/ops/ConstNode.cpp @@ -1,5 +1,8 @@ #include "../../bridge/Exceptions.h" +#include "../../tools/RTValueWrapper.h" #include "../CodeGen.h" +#include "codegen/TypedValue.h" +#include using namespace std; using namespace llvm; @@ -84,17 +87,21 @@ TypedValue CodeGen::codegen(const Node &node, const ConstNode &subnode, retVal = dynamicConstructor.createRatio(subnode.val().c_str()); memoryManagement.dynamicRetain(retVal); break; - // case varType: - // { - // Var *var = TheProgramme->getVarByName(subnode.val()); - // if (!var) { - // throwCodeGenerationException("Unable to resolve var: " + - // subnode.val() + " in this context", node); - // } - // retVal = - // Builder->CreateBitOrPointerCast(ConstantInt::get(Type::getInt64Ty(*TheContext), - // APInt(64, (uint64_t) var, false)), ptrT); dynamicRetain(retVal); break; - // } + case varType: { + const std::string name = subnode.val(); + ScopedRef var(compilerState.varRegistry.getCurrent(name.c_str())); + if (!var) { + throwCodeGenerationException( + string("Unable to resolve var: ") + name + " in this context", node); + } + uint64_t address = reinterpret_cast(var.get()); + retVal = TypedValue( + ObjectTypeSet(varType), + ConstantExpr::getIntToPtr(ConstantInt::get(this->types.i64Ty, address), + this->types.ptrTy)); + memoryManagement.dynamicRetain(retVal); + break; + } case persistentListType: case persistentVectorType: @@ -139,7 +146,8 @@ ObjectTypeSet CodeGen::getType(const Node &node, const ConstNode &subnode, } if (node.tag() == "double" || node.otag() == "double" || - node.tag() == "class java.lang.Double" || subnode.val().find('.') != string::npos) { + node.tag() == "class java.lang.Double" || + subnode.val().find('.') != string::npos) { return ObjectTypeSet(doubleType, false, new ConstantDouble(stod(subnode.val()))) .restriction(typeRestrictions); @@ -162,10 +170,11 @@ ObjectTypeSet CodeGen::getType(const Node &node, const ConstNode &subnode, } } } catch (...) { - if (!subnode.val().empty() && (isdigit(subnode.val()[0]) || subnode.val()[0] == '-')) { - return ObjectTypeSet(bigIntegerType, true, - new ConstantBigInteger(subnode.val())) - .restriction(typeRestrictions); + if (!subnode.val().empty() && + (isdigit(subnode.val()[0]) || subnode.val()[0] == '-')) { + return ObjectTypeSet(bigIntegerType, true, + new ConstantBigInteger(subnode.val())) + .restriction(typeRestrictions); } } } @@ -192,10 +201,9 @@ ObjectTypeSet CodeGen::getType(const Node &node, const ConstNode &subnode, .restriction(typeRestrictions); // case ConstNode_ConstType_constTypeClass: // return ObjectTypeSet(classType).restriction(typeRestrictions); - // case ConstNode_ConstType_constTypeVar: - // return ObjectTypeSet(varType).restriction(typeRestrictions); + case ConstNode_ConstType_constTypeVar: + return ObjectTypeSet(varType).restriction(typeRestrictions); case ConstNode_ConstType_constTypeType: - case ConstNode_ConstType_constTypeRecord: case ConstNode_ConstType_constTypeMap: case ConstNode_ConstType_constTypeVector: diff --git a/backend-v2/codegen/ops/DefNode.cpp b/backend-v2/codegen/ops/DefNode.cpp new file mode 100644 index 00000000..91314b31 --- /dev/null +++ b/backend-v2/codegen/ops/DefNode.cpp @@ -0,0 +1,44 @@ +#include "../CodeGen.h" +#include "bridge/Exceptions.h" +#include "tools/RTValueWrapper.h" +#include "types/ObjectTypeSet.h" + +using namespace std; +using namespace llvm; + +namespace rt { + +TypedValue CodeGen::codegen(const Node &node, const DefNode &subnode, + const ObjectTypeSet &typeRestrictions) { + auto varName = subnode.var().substr(2); + ScopedRef var(getOrCreateVar(varName)); + uint64_t address = reinterpret_cast(var.get()); + TypedValue varPtr = TypedValue( + ObjectTypeSet(varType), + ConstantExpr::getIntToPtr(ConstantInt::get(this->types.i64Ty, address), + this->types.ptrTy)); + memoryManagement.dynamicRetain(varPtr); + if (subnode.has_init()) { + TypedValue initValue = codegen(subnode.init(), ObjectTypeSet::all()); + memoryManagement.dynamicRetain(varPtr); + invokeManager.invokeRuntime( + "Var_bindRoot", nullptr, + {ObjectTypeSet(varType), ObjectTypeSet::dynamicType()}, + {varPtr, initValue}); + } + + return varPtr; +} + +ObjectTypeSet CodeGen::getType(const Node &node, const DefNode &subnode, + const ObjectTypeSet &typeRestrictions) { + auto varName = subnode.var().substr(2); + if (!typeRestrictions.contains(varType)) { + throwCodeGenerationException( + "Def cannot be used here because it returns var: " + varName, node); + } + getOrCreateVar(varName); + return ObjectTypeSet(varType); +} + +} // namespace rt diff --git a/backend-v2/codegen/ops/TheVarNode.cpp b/backend-v2/codegen/ops/TheVarNode.cpp new file mode 100644 index 00000000..2bf1273c --- /dev/null +++ b/backend-v2/codegen/ops/TheVarNode.cpp @@ -0,0 +1,31 @@ +#include "../CodeGen.h" +#include "bridge/Exceptions.h" +#include "../../tools/RTValueWrapper.h" + +using namespace std; +using namespace llvm; + +namespace rt { + +TypedValue CodeGen::codegen(const Node &node, const TheVarNode &subnode, + const ObjectTypeSet &typeRestrictions) { + auto varName = subnode.var().substr(2); + ScopedRef var(compilerState.varRegistry.getCurrent(varName.c_str())); + if (!var) + throwCodeGenerationException( + "Unable to resolve var: " + varName + " in this context", node); + uintptr_t address = reinterpret_cast(var.get()); + auto retVal = TypedValue( + ObjectTypeSet(varType), + ConstantExpr::getIntToPtr(ConstantInt::get(this->types.i64Ty, address), + this->types.ptrTy)); + memoryManagement.dynamicRetain(retVal); + return retVal; +} + +ObjectTypeSet CodeGen::getType(const Node &node, const TheVarNode &subnode, + const ObjectTypeSet &typeRestrictions) { + return ObjectTypeSet(varType); +} + +} // namespace rt \ No newline at end of file diff --git a/backend-v2/codegen/ops/VarNode.cpp b/backend-v2/codegen/ops/VarNode.cpp new file mode 100644 index 00000000..fc9dd3b3 --- /dev/null +++ b/backend-v2/codegen/ops/VarNode.cpp @@ -0,0 +1,34 @@ +#include "../CodeGen.h" +#include "bridge/Exceptions.h" +#include "../../tools/RTValueWrapper.h" + +using namespace std; +using namespace llvm; + +namespace rt { + +TypedValue CodeGen::codegen(const Node &node, const VarNode &subnode, + const ObjectTypeSet &typeRestrictions) { + auto varName = subnode.var().substr(2); + ScopedRef var(compilerState.varRegistry.getCurrent(varName.c_str())); + if (!var) + throwCodeGenerationException( + "Unable to resolve var: " + varName + " in this context", node); + + uintptr_t address = reinterpret_cast(var.get()); + TypedValue varPtr = TypedValue( + ObjectTypeSet(varType), + ConstantExpr::getIntToPtr(ConstantInt::get(this->types.i64Ty, address), + this->types.ptrTy)); + memoryManagement.dynamicRetain(varPtr); + + return invokeManager.invokeRuntime("Var_deref", nullptr, + {ObjectTypeSet(varType)}, {varPtr}); +} + +ObjectTypeSet CodeGen::getType(const Node &node, const VarNode &subnode, + const ObjectTypeSet &typeRestrictions) { + return ObjectTypeSet::all(); +} + +} // namespace rt diff --git a/backend-v2/main.cpp b/backend-v2/main.cpp index 37ad6de2..06d569d6 100644 --- a/backend-v2/main.cpp +++ b/backend-v2/main.cpp @@ -2,6 +2,7 @@ #include "bytecode.pb.h" #include #include +#include #include "bridge/Exceptions.h" #include "llvm/ADT/APFloat.h" @@ -24,6 +25,7 @@ #include "llvm/Transforms/Scalar/GVN.h" #include "jit/JITEngine.h" +#include "runtime/String.h" #include "state/ThreadsafeCompilerState.h" using namespace std; @@ -92,18 +94,23 @@ int main(int argc, char *argv[]) { state.storeInternalClasses(classes); - auto f = engine.compileAST(astRoot.nodes(0), "__root", - llvm::OptimizationLevel::O0, true); - cout << "Compiling!!!" << endl; - - RTValue whaat = f.get().toPtr()(); - String *s = toString(whaat); - s = String_compactify(s); - - cout << "========== Result ==========" << endl; - cout << std::string(String_c_str(s)) << endl; - cout << "========== /Result ==========" << endl; - Ptr_release(s); + for (int j = 0; j < astRoot.nodes_size(); j++) { + auto topLevelNode = astRoot.nodes(j); + cout << "=============================" << endl; + cout << "Compiling!!!" << endl; + std::string moduleName = "__repl__" + std::to_string(j); + auto f = engine.compileAST(topLevelNode, moduleName, + llvm::OptimizationLevel::O0, true); + + RTValue whaat = f.get().toPtr()(); + String *s = toString(whaat); + s = String_compactify(s); + + cout << "========== Result ==========" << endl; + cout << std::string(String_c_str(s)) << endl; + cout << "========== /Result ==========" << endl; + Ptr_release(s); + } retVal = 0; } catch (rt::LanguageException e) { cout << rt::getExceptionString(e) << endl; diff --git a/backend-v2/runtime/CMakeLists.txt b/backend-v2/runtime/CMakeLists.txt index d41309b4..7b33e5a9 100644 --- a/backend-v2/runtime/CMakeLists.txt +++ b/backend-v2/runtime/CMakeLists.txt @@ -34,7 +34,8 @@ set(SOURCES Ratio.c Class.c RuntimeInterface.c - PersistentArrayMap.c) + PersistentArrayMap.c + Var.c) add_library(runtime STATIC ${SOURCES}) @@ -67,6 +68,8 @@ set(TEST_MODULES Integer_test Ratio_test Boolean_test + Var_test + Var_Race_test ) foreach(TEST_MODULE ${TEST_MODULES}) diff --git a/backend-v2/runtime/Object.h b/backend-v2/runtime/Object.h index 51d1bc8e..19d1b990 100644 --- a/backend-v2/runtime/Object.h +++ b/backend-v2/runtime/Object.h @@ -51,6 +51,7 @@ using std::memory_order_seq_cst; #include "Ratio.h" #include "String.h" #include "Symbol.h" +#include "Var.h" typedef struct String String; @@ -238,6 +239,10 @@ inline void Object_destroy(Object *restrict self, bool deallocateChildren) { case persistentArrayMapType: PersistentArrayMap_destroy((PersistentArrayMap *)self, deallocateChildren); break; + case varType: + Var_destroy((Var *)self); + break; + default: assert(false && "Internal error: hash computation for NaN tagged types " "should be computed earlier."); @@ -327,6 +332,8 @@ inline uword_t Object_hash(Object *restrict self) { return Ratio_hash((Ratio *)self); case persistentArrayMapType: return PersistentArrayMap_hash((PersistentArrayMap *)self); + case varType: + return Var_hash((Var *)self); default: assert(false && "Internal error: hash computation for NaN tagged types " "should be computed earlier."); @@ -403,6 +410,11 @@ inline bool Object_equals(Object *self, Object *other) { return PersistentArrayMap_equals((PersistentArrayMap *)self, (PersistentArrayMap *)other); break; + case varType: + return Var_equals((Var *)self, + (Var *)other); + break; + default: assert(false && "Internal error: hash computation for NaN tagged types " "should be computed earlier."); @@ -459,6 +471,8 @@ inline String *Object_toString(Object *restrict self) { return Ratio_toString((Ratio *)self); case persistentArrayMapType: return PersistentArrayMap_toString((PersistentArrayMap *)self); + case varType: + return Var_toString((Var *)self); default: assert(false && "Internal error: Object_toString got an unsupported type"); } diff --git a/backend-v2/runtime/ObjectProto.h b/backend-v2/runtime/ObjectProto.h index bce5ca01..fdd06f4a 100644 --- a/backend-v2/runtime/ObjectProto.h +++ b/backend-v2/runtime/ObjectProto.h @@ -38,6 +38,7 @@ enum objectType { ratioType, // 14 classType, // 15 persistentArrayMapType, // 16 + varType, // 17 }; typedef enum objectType objectType; diff --git a/backend-v2/runtime/RuntimeInterface.c b/backend-v2/runtime/RuntimeInterface.c index b65e11c3..d8cf0996 100644 --- a/backend-v2/runtime/RuntimeInterface.c +++ b/backend-v2/runtime/RuntimeInterface.c @@ -3,6 +3,7 @@ #include "Keyword.h" #include "PersistentVector.h" #include "Symbol.h" +#include "Var.h" #include #include @@ -29,6 +30,7 @@ void RuntimeInterface_initialise() { vars = ConcurrentHashMap_create(10); // 2^10 symbols = ConcurrentHashMap_create(10); symbolsInverted = ConcurrentHashMap_create(10); + Var_initialize(); } void RuntimeInterface_cleanup() { @@ -53,11 +55,12 @@ void RuntimeInterface_cleanup() { vars = NULL; } PersistentVector_cleanup(); + Var_cleanup(); } void printReferenceCounts() { printf("Ref counters: "); - for (unsigned char i = integerType; i <= persistentArrayMapType; i++) { + for (unsigned char i = integerType; i <= varType; i++) { printf("%lu/%lu ", allocationCount[i - 1], objectCount[i - 1]); } printf("\n"); diff --git a/backend-v2/runtime/Var.c b/backend-v2/runtime/Var.c new file mode 100644 index 00000000..f6444401 --- /dev/null +++ b/backend-v2/runtime/Var.c @@ -0,0 +1,260 @@ +#include "Var.h" +#include "Object.h" +#include "RTValue.h" +#include "String.h" +#include "word.h" +#include +#include + +// TODO: UnboundClass is printed in different way +// Class *UNIQUE_UnboundClass; + +#if defined(__x86_64__) || defined(__i386__) +/* Works for both Intel Macs and Linux on x86 */ +#include +#define CPU_PAUSE() _mm_pause() +#elif defined(__aarch64__) || defined(__arm__) +/* Works for Apple Silicon (M1/M2/M3) and ARM Linux */ +#if defined(__GNUC__) || defined(__clang__) +#define CPU_PAUSE() __asm__ __volatile__("yield") +#else +/* Fallback for other compilers on ARM */ +#define CPU_PAUSE() __builtin_arm_yield() +#endif +#else +/* No-op for unknown architectures */ +#define CPU_PAUSE() \ + do { \ + } while (0) +#endif + +typedef struct HazardSlot { + _Atomic(RTValue) hazardPointer; + _Atomic(bool) active; + struct HazardSlot *next; +} HazardSlot; + +static _Atomic(HazardSlot *) hazardHead = NULL; +static __thread HazardSlot *threadLocalHazardSlot = NULL; +static pthread_key_t cleanup_gatekeeper; + +static void *dummy_page = NULL; +static void asymmetric_barrier() { + if (!dummy_page) { + dummy_page = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + } + // Toggle protection to force TLB shootdown / System-wide barrier on all cores + mprotect(dummy_page, getpagesize(), PROT_READ); + mprotect(dummy_page, getpagesize(), PROT_READ | PROT_WRITE); +} + +void on_thread_exit(void *unused) { + if (threadLocalHazardSlot != NULL) { + bool expectedValue = true; + atomic_compare_exchange_weak(&(threadLocalHazardSlot->active), + &expectedValue, false); + } +} + +void Var_initialize() { + pthread_key_create(&cleanup_gatekeeper, on_thread_exit); +} + +void Var_thread_initialize() { + pthread_setspecific(cleanup_gatekeeper, (void *)0x1); +} + +void Var_cleanup() { /* to be run when all other threads are dead */ + HazardSlot *current_head; + while ((current_head = atomic_load(&hazardHead)) != NULL) { + HazardSlot *next_head = current_head->next; + if (!atomic_compare_exchange_weak(&hazardHead, ¤t_head, next_head)) { + continue; + } + free(current_head); + } +} + +static inline HazardSlot *getOrCreateSlot() { + if (threadLocalHazardSlot) + return threadLocalHazardSlot; + + // 1. Try to find an inactive slot to recycle + HazardSlot *curr = atomic_load(&hazardHead); + while (curr) { + bool expected = false; + if (atomic_compare_exchange_strong(&curr->active, &expected, true)) { + threadLocalHazardSlot = curr; + return curr; + } + curr = curr->next; + } + + // 2. No free slots, allocate new + HazardSlot *new_node = calloc(1, sizeof(HazardSlot)); + new_node->active = true; + + // Lock-free push to the global list + HazardSlot *old_head; + do { + old_head = atomic_load(&hazardHead); + new_node->next = old_head; + } while (!atomic_compare_exchange_weak(&hazardHead, &old_head, new_node)); + + threadLocalHazardSlot = new_node; + return threadLocalHazardSlot; +} + +Var *Var_create(RTValue keyword) { + Var *self = (Var *)allocate(sizeof(Var)); + // retain(UNIQUE_UnboundClass); + atomic_init(&(self->root), + RT_boxNull()); //(Object *)Deftype_create(UNIQUE_UnboundClass, 1, + // keyword); + self->dynamic = false; + self->keyword = keyword; + atomic_init(&self->rev, 0); + Object_create((Object *)self, varType); + String *kw = String_compactify(toString(self->keyword)); + printf("Var_create: %s\n", String_c_str(kw)); + Ptr_release(kw); + return self; +}; + +bool Var_equals(Var *self, Var *other) { + return false; // pointer equality in Object_equals +}; + +uword_t Var_hash(Var *self) { + return combineHash(hash(self->keyword), hash(self->root)); +}; + +String *Var_toString(Var *self) { + String *retVal = String_create("#"); + retVal = String_concat(retVal, String_replace(toString(self->keyword), + String_create(":"), + String_create("'"))); + Ptr_release(self); + return retVal; +}; + +void Var_destroy(Var *self) { + release(self->root); + release(self->keyword); +}; + +Var *Var_setDynamic(Var *self, bool dynamic) { // modifies and returns self + self->dynamic = dynamic; + return self; +}; + +bool Var_isDynamic(Var *self) { + bool retVal = self->dynamic; + Ptr_release(self); + return retVal; +}; + +bool Var_hasRoot(Var *self) { + bool retVal = + atomic_load_explicit(&self->root, memory_order_acquire) != RT_boxNull(); + Ptr_release(self); + return retVal; +}; + +RTValue Var_deref(Var *self) { + HazardSlot *slot = getOrCreateSlot(); + + while (true) { + RTValue val = atomic_load_explicit(&self->root, memory_order_acquire); + if (val == RT_boxNull()) { + Ptr_release(self); + return val; + } + // Stage 1: Advertise + // Relaxed store: writer will force visibility with asymmetric_barrier() + atomic_store_explicit(&slot->hazardPointer, val, memory_order_relaxed); + + // Prevent compiler from reordering the load of root before the slot store + atomic_signal_fence(memory_order_seq_cst); + + // Stage 2: Verify (The "Double Check") + // Acquire load is enough here because writer ensures its update is visible + // before we'd potentially use the old freed value. + if (val == atomic_load_explicit(&self->root, memory_order_acquire)) { + retain(val); + atomic_store_explicit(&slot->hazardPointer, RT_boxNull(), + memory_order_relaxed); + Ptr_release(self); + return val; + } + + // Global changed, retry + atomic_store_explicit(&slot->hazardPointer, RT_boxNull(), + memory_order_relaxed); + } + // TODO: threadBound +}; + +RTValue Var_bindRoot(Var *self, RTValue object) { + RTValue oldRoot = + atomic_exchange_explicit(&self->root, object, memory_order_seq_cst); + atomic_fetch_add_explicit(&(self->rev), 1, memory_order_relaxed); + + if (oldRoot != RT_boxNull()) { + // Heavy synchronization: ensure all readers see the update + // and writer sees all reader advertisements. + asymmetric_barrier(); + + HazardSlot *newHead = atomic_load(&hazardHead); + HazardSlot *curr; + HazardSlot *head; + + do { + head = newHead; + curr = head; + while (curr) { + while (atomic_load_explicit(&curr->hazardPointer, + memory_order_seq_cst) == oldRoot) { + CPU_PAUSE(); + } + curr = curr->next; + } + newHead = atomic_load(&hazardHead); + } while (newHead != head); + } + + release(oldRoot); + Ptr_release(self); + return RT_boxNil(); +} + +RTValue Var_unbindRoot(Var *self) { + RTValue oldRoot = + atomic_exchange_explicit(&self->root, RT_boxNull(), memory_order_seq_cst); + atomic_fetch_add_explicit(&(self->rev), 1, memory_order_relaxed); + + if (oldRoot != RT_boxNull()) { + asymmetric_barrier(); + HazardSlot *newHead = atomic_load(&hazardHead); + HazardSlot *curr; + HazardSlot *head; + + do { + head = newHead; + curr = head; + while (curr) { + while (atomic_load_explicit(&curr->hazardPointer, + memory_order_seq_cst) == oldRoot) { + CPU_PAUSE(); + } + curr = curr->next; + } + newHead = atomic_load(&hazardHead); + } while (newHead != head); + } + + release(oldRoot); + Ptr_release(self); + return RT_boxNil(); +} diff --git a/backend-v2/runtime/Var.h b/backend-v2/runtime/Var.h new file mode 100644 index 00000000..5d1936db --- /dev/null +++ b/backend-v2/runtime/Var.h @@ -0,0 +1,51 @@ +#ifndef RT_VAR +#define RT_VAR + +#include "word.h" +#ifdef __cplusplus +extern "C" { +#endif + +#include "Keyword.h" +#include "Nil.h" +#include "RTValue.h" +#include "String.h" + +typedef struct Object Object; + +struct Var { + Object super; + bool dynamic; + _Atomic(uword_t) rev; + _Atomic(RTValue) root; + RTValue keyword; // TODO: split name and namespace - Marek - why? + + // TODO: threadBound +}; + +typedef struct Var Var; + +// Class *UNIQUE_UnboundClass; + +Var *Var_create(RTValue keyword); +bool Var_equals(Var *self, Var *other); +uword_t Var_hash(Var *self); +String *Var_toString(Var *self); +void Var_destroy(Var *self); + +Var *Var_setDynamic(Var *self, bool dynamic); // modifies and returns self +bool Var_isDynamic(Var *self); +bool Var_hasRoot(Var *self); +RTValue Var_deref(Var *self); +RTValue Var_bindRoot(Var *self, RTValue object); +RTValue Var_unbindRoot(Var *self); + +void Var_initialize(); +void Var_thread_initialize(); +void Var_cleanup(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/backend-v2/runtime/tests/Var_Race_test.c b/backend-v2/runtime/tests/Var_Race_test.c new file mode 100644 index 00000000..a7d5cfe5 --- /dev/null +++ b/backend-v2/runtime/tests/Var_Race_test.c @@ -0,0 +1,104 @@ +#include "TestTools.h" +#include +#include +#include + +#include "../Keyword.h" +#include "../RuntimeInterface.h" +#include "../String.h" +#include "../Var.h" +#include "TestTools.h" + +#define ITERATIONS 1000000 + +static atomic_bool stop_threads = false; + +struct ThreadArgs { + Var *v; +}; + +void *writer_thread(void *arg) { + Var_thread_initialize(); + struct ThreadArgs *args = (struct ThreadArgs *)arg; + Var *v = args->v; + Ptr_retain(v); + + int i = 0; + for (i = 0; i < ITERATIONS && !atomic_load(&stop_threads); ++i) { + // Create a new string and bind it as root + RTValue val = RT_boxPtr(String_create("new-value")); + Ptr_retain(v); + Var_bindRoot(v, val); + // Var_bindRoot releases the old value, and it was the only owner. + // If the reader thread was just about to retain it, we have a UAF. + } + Ptr_release(v); + printf("!!! Terminating writer thread after %d iterations\n", i); + atomic_store(&stop_threads, true); + return NULL; +} + +void *reader_thread(void *arg) { + Var_thread_initialize(); + struct ThreadArgs *args = (struct ThreadArgs *)arg; + Var *v = args->v; + Ptr_retain(v); + int i = 0; + for (i = 0; i < ITERATIONS && !atomic_load(&stop_threads); ++i) { + // Deref the var. This reads v->root and then retains it. + // There is no lock between reading and retaining. + Ptr_retain(v); + RTValue val = Var_deref(v); + + // Use the value to trigger a crash if it was freed + if (RT_isPtr(val)) { + String *s = (String *)RT_unboxPtr(val); + // This might crash if s was already freed and its memory reused/poisoned + const char *c_str = String_c_str(s); + (void)c_str; + } + + release(val); + } + Ptr_release(v); + printf("!!! Terminating reader thread after %d iterations\n", i); + atomic_store(&stop_threads, true); + return NULL; +} + +static void test_var_race_condition(void **state) { + ASSERT_MEMORY_ALL_BALANCED({ + RTValue sym = Keyword_create(String_create("race-var")); + Var *v = Var_create(sym); + + // Initial binding + Ptr_retain(v); + Var_bindRoot(v, RT_boxPtr(String_create("initial-value"))); + + struct ThreadArgs args = {.v = v}; + pthread_t writer; + pthread_t reader; + + pthread_create(&writer, NULL, writer_thread, &args); + pthread_create(&reader, NULL, reader_thread, &args); + + pthread_join(writer, NULL); + pthread_join(reader, NULL); + + // If we reached here without a crash, the race didn't trigger in this run. + // In a real scenario, we might want to run this in a loop or with ASan. + + // Cleanup (might also crash if memory is corrupted) + Var_unbindRoot(v); + }); +} + +int main(int argc, char **argv) { + initialise_memory(); + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_var_race_condition), + }; + int result = cmocka_run_group_tests(tests, NULL, NULL); + RuntimeInterface_cleanup(); + return result; +} diff --git a/backend-v2/runtime/tests/Var_test.c b/backend-v2/runtime/tests/Var_test.c new file mode 100644 index 00000000..f1555b71 --- /dev/null +++ b/backend-v2/runtime/tests/Var_test.c @@ -0,0 +1,87 @@ +#include "TestTools.h" +#include + +#include "../Keyword.h" +#include "../RuntimeInterface.h" +#include "../String.h" +#include "../Var.h" + +static void test_var_basic_lifecycle(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + RTValue sym = Keyword_create(String_create("my-var")); + Var *v = Var_create(sym); + + Ptr_retain(v); + assert_true(Var_isDynamic(v) == false); + + Ptr_retain(v); + assert_true(Var_hasRoot(v) == false); + + RTValue val = RT_boxPtr(String_create("root-value")); + Ptr_retain(v); + Var_bindRoot(v, val); + + Ptr_retain(v); + assert_true(Var_hasRoot(v) == true); + + Ptr_retain(v); + RTValue deref = Var_deref(v); + assert_string_equal(String_c_str(RT_unboxPtr(deref)), "root-value"); + + release(deref); + + Ptr_retain(v); + Var_unbindRoot(v); + + assert_true(Var_hasRoot(v) == false); + }); +} + +static void test_var_dynamic(void **state) { + (void)state; + ASSERT_MEMORY_ALL_BALANCED({ + RTValue sym = Keyword_create(String_create("dynamic-var")); + Var *v = Var_create(sym); + + v = Var_setDynamic(v, true); + Ptr_retain(v); + assert_true(Var_isDynamic(v) == true); + + v = Var_setDynamic(v, false); + Ptr_retain(v); + assert_true(Var_isDynamic(v) == false); + + Ptr_release(v); + release(sym); + }); +} + +static void test_var_tostring(void **state) { + (void)state; + + ASSERT_MEMORY_ALL_BALANCED({ + RTValue sym = Keyword_create(String_create("foo")); + Var *v = Var_create(sym); + + // Var_toString consumes self and may return a compound string + String *s = Var_toString(v); + s = String_compactify(s); + assert_string_equal(String_c_str(s), "#'foo"); + + Ptr_release(s); + release(sym); + }); +} + +int main(int argc, char **argv) { + initialise_memory(); + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_var_basic_lifecycle), + cmocka_unit_test(test_var_dynamic), + cmocka_unit_test(test_var_tostring), + }; + int result = cmocka_run_group_tests(tests, NULL, NULL); + RuntimeInterface_cleanup(); + return result; +} diff --git a/backend-v2/state/ThreadsafeCompilerState.h b/backend-v2/state/ThreadsafeCompilerState.h index 36392644..ded3a397 100644 --- a/backend-v2/state/ThreadsafeCompilerState.h +++ b/backend-v2/state/ThreadsafeCompilerState.h @@ -11,7 +11,7 @@ extern "C" { #include "runtime/Object.h" #include "runtime/ObjectProto.h" #include "runtime/PersistentArrayMap.h" -/* #include "runtime/Var.h" */ +#include "runtime/Var.h" } using namespace clojure::rt::protobuf::bytecode; @@ -24,9 +24,10 @@ class ThreadsafeCompilerState { ThreadsafeRegistry<::Class> classRegistry; ThreadsafeRegistry functionAstRegistry; - /* ThreadsafeRegistry varRegistry; */ + ThreadsafeRegistry<::Var> varRegistry; - ThreadsafeCompilerState() : classRegistry(true), functionAstRegistry(false) {} + ThreadsafeCompilerState() + : classRegistry(true), functionAstRegistry(false), varRegistry(true) {} void storeInternalClasses(RTValue from); void storeInternalProtocols(RTValue from); diff --git a/backend-v2/state/ThreadsafeRegistry.h b/backend-v2/state/ThreadsafeRegistry.h index 6bf40392..c54684b8 100644 --- a/backend-v2/state/ThreadsafeRegistry.h +++ b/backend-v2/state/ThreadsafeRegistry.h @@ -3,6 +3,7 @@ #include "../RuntimeHeaders.h" #include "../bridge/Exceptions.h" +#include "runtime/Object.h" #include #include #include @@ -50,6 +51,25 @@ template class ThreadsafeRegistry { registry[name] = newDef; } + template T *getOrCreate(const char *name, F &&factory) { + std::lock_guard lock(registryMutex); + + std::string key(name); + auto it = registry.find(key); + + if (it != registry.end()) { + if (manageRuntimeMemory) + Ptr_retain((void *)it->second); + return it->second; + } + + T *newDef = factory(); + registry[key] = newDef; + if (manageRuntimeMemory) + Ptr_retain(newDef); + return newDef; + } + T *getCurrent(const int32_t index) const { std::lock_guard lock(registryMutex); auto it = indexedRegistry.find(index); diff --git a/backend-v2/tools/RTValueWrapper.h b/backend-v2/tools/RTValueWrapper.h index 67988858..227ece88 100644 --- a/backend-v2/tools/RTValueWrapper.h +++ b/backend-v2/tools/RTValueWrapper.h @@ -87,6 +87,8 @@ template class PtrWrapper { operator bool() const { return ptr != nullptr; } }; +template using ScopedRef = PtrWrapper; + } // namespace rt #endif diff --git a/backend-v2/types/ObjectTypeSet.h b/backend-v2/types/ObjectTypeSet.h index ec8f6879..57874ac2 100644 --- a/backend-v2/types/ObjectTypeSet.h +++ b/backend-v2/types/ObjectTypeSet.h @@ -244,6 +244,7 @@ class ObjectTypeSet { retVal.insert(ratioType); retVal.insert(classType); retVal.insert(persistentArrayMapType); + retVal.insert(varType); retVal.isBoxed = true; return retVal; } @@ -295,6 +296,8 @@ class ObjectTypeSet { return "LC"; case persistentArrayMapType: return "LA"; + case varType: + return "LQ"; } } diff --git a/backend/runtime/Object.h b/backend/runtime/Object.h index cc03ecbe..3f10e66e 100644 --- a/backend/runtime/Object.h +++ b/backend/runtime/Object.h @@ -30,7 +30,7 @@ #include "Class.h" #include "Deftype.h" #include "Function.h" -#include "Var.h" +//#include "Var.h" #include "BigInteger.h" #include "Ratio.h" #include "PersistentArrayMap.h" diff --git a/frontend/resources/rt-classes.edn b/frontend/resources/rt-classes.edn index aeb4c66c..176ea011 100644 --- a/frontend/resources/rt-classes.edn +++ b/frontend/resources/rt-classes.edn @@ -225,7 +225,7 @@ clojure.lang.Util {:static-fns - {equiv [{:args [:int :int] :type :intrinsic :symbol "ICmpEQ" :returns :bool} + {equiv [{:args [:int :int] :type :intrinsic :symbol "ICmpEQ" :returns :bool} {:args [:double :double] :type :intrinsic :symbol "FCmpOEQ" :returns :bool} {:args [:bigint :double] :type :intrinsic :symbol "FCmpOEQ_BD" :returns :bool} {:args [:bigint :int] :type :call :symbol "BigInteger_equalsInt" :returns :bool} diff --git a/frontend/src/clojure/rt/protobuf/encoder.clj b/frontend/src/clojure/rt/protobuf/encoder.clj index 9e8ecc05..15cefdb4 100644 --- a/frontend/src/clojure/rt/protobuf/encoder.clj +++ b/frontend/src/clojure/rt/protobuf/encoder.clj @@ -15,7 +15,7 @@ n)))) (def all-keys-types {:op :op - :form :string + :form :pr-str :env :environment :raw-forms [:string] :top-level :bool @@ -50,7 +50,8 @@ node-type (op node-key-types) proto-symbol (comp keyword cl->pt) - converters {:string str + converters {:pr-str pr-str + :string str :int identity :bool identity :node (partial encode-node node-key-types) diff --git a/tests/zulugula.clj b/tests/zulugula.clj new file mode 100644 index 00000000..da28341d --- /dev/null +++ b/tests/zulugula.clj @@ -0,0 +1,8 @@ +;; Currently this collapses (unable to generate dynamic call) and this is good +;; because dynamic calls to equiv are not yet implemented. +;; What is not good is that var leaks from the test. +;; It is not yet clear why, but there is an exception being thrown in code generation, maybe that's the reason? +;; Also - threadsafe registry always returns stuff at +1, so there should be a release if we cannot proceed? + +(def a 5) +(if (= a 5) "zulu" "gula") \ No newline at end of file