diff --git a/src/Amalgam/entity/Entity.cpp b/src/Amalgam/entity/Entity.cpp index 6c93ebc1..baa2527c 100644 --- a/src/Amalgam/entity/Entity.cpp +++ b/src/Amalgam/entity/Entity.cpp @@ -425,7 +425,8 @@ EvaluableNodeReference Entity::Execute(ExecutionCycleCount max_num_steps, Execut Concurrency::ReadLock *locked_memory_modification_lock, Concurrency::WriteLock *entity_write_lock, #endif - StringInternPool::StringID label_sid, Interpreter *calling_interpreter) + StringInternPool::StringID label_sid, + Interpreter *calling_interpreter, bool copy_call_stack) { if(!on_self && IsLabelPrivate(label_sid)) return EvaluableNodeReference(nullptr, true); @@ -468,6 +469,9 @@ EvaluableNodeReference Entity::Execute(ExecutionCycleCount max_num_steps, Execut entity_write_lock->unlock(); #endif + if(copy_call_stack) + call_stack = evaluableNodeManager.DeepAllocCopy(call_stack); + EvaluableNodeReference retval = interpreter.ExecuteNode(node_to_execute, call_stack); num_steps_executed = interpreter.GetNumStepsExecuted(); diff --git a/src/Amalgam/entity/Entity.h b/src/Amalgam/entity/Entity.h index 0b463d92..7ab3aa50 100644 --- a/src/Amalgam/entity/Entity.h +++ b/src/Amalgam/entity/Entity.h @@ -57,6 +57,7 @@ class Entity // If locked_memory_modification_lock is specified, then it will unlock it prior to the execution, but lock it again before // If entity_write_lock is specified, then it will unlock prior to execution after locked_memory_modification_lock is locked // potentially writing anything out to destination_temp_enm + // If copy_call_stack is true, it will copy call_stack into the evaluableNodeManager managed by this entity while under appropriate locks EvaluableNodeReference Execute(ExecutionCycleCount max_num_steps, ExecutionCycleCount &num_steps_executed, size_t max_num_nodes, size_t &num_nodes_allocated, std::vector *write_listeners, PrintListener *print_listener, EvaluableNode *call_stack = nullptr, bool on_self = false, EvaluableNodeManager *destination_temp_enm = nullptr, @@ -65,7 +66,7 @@ class Entity Concurrency::WriteLock *entity_write_lock = nullptr, #endif StringInternPool::StringID label_sid = StringInternPool::NOT_A_STRING_ID, - Interpreter *calling_interpreter = nullptr); + Interpreter *calling_interpreter = nullptr, bool copy_call_stack = false); //same as Execute but accepts a string for label name inline EvaluableNodeReference Execute(ExecutionCycleCount max_num_steps, ExecutionCycleCount &num_steps_executed, @@ -77,7 +78,7 @@ class Entity Concurrency::WriteLock *entity_write_lock, #endif const std::string &label_name, - Interpreter *calling_interpreter = nullptr) + Interpreter *calling_interpreter = nullptr, bool copy_call_stack = false) { StringInternPool::StringID label_sid = string_intern_pool.GetIDFromString(label_name); return Execute(max_num_steps, num_steps_executed, max_num_nodes, num_nodes_allocated, write_listeners, print_listener, @@ -85,7 +86,7 @@ class Entity #ifdef MULTITHREAD_SUPPORT locked_memory_modification_lock, entity_write_lock, #endif - label_sid, calling_interpreter); + label_sid, calling_interpreter, copy_call_stack); } //returns true if the entity or any of its contained entities are currently being executed, either because of multiple threads executing on it diff --git a/src/Amalgam/evaluablenode/EvaluableNodeManagement.cpp b/src/Amalgam/evaluablenode/EvaluableNodeManagement.cpp index 2d0f52ad..b4293f65 100644 --- a/src/Amalgam/evaluablenode/EvaluableNodeManagement.cpp +++ b/src/Amalgam/evaluablenode/EvaluableNodeManagement.cpp @@ -8,6 +8,10 @@ #include #include +#ifdef MULTITHREAD_SUPPORT +Concurrency::ReadWriteMutex EvaluableNodeManager::memoryModificationMutex; +#endif + const double EvaluableNodeManager::allocExpansionFactor = 1.5; const ExecutionCycleCountCompactDelta EvaluableNodeManager::minCycleCountBetweenGarbageCollects = 150000; diff --git a/src/Amalgam/evaluablenode/EvaluableNodeManagement.h b/src/Amalgam/evaluablenode/EvaluableNodeManagement.h index 78ecb617..a48a88ee 100644 --- a/src/Amalgam/evaluablenode/EvaluableNodeManagement.h +++ b/src/Amalgam/evaluablenode/EvaluableNodeManagement.h @@ -620,18 +620,20 @@ class EvaluableNodeManager static void ValidateEvaluableNodeTreeMemoryIntegrityRecurse(EvaluableNode *en, EvaluableNode::ReferenceSetType &checked); -#ifdef MULTITHREAD_SUPPORT +#ifdef MULTITHREAD_SUPPORT public: //mutex to manage attributes of manager, including operations such as // memory allocation, reference management, etc. Concurrency::ReadWriteMutex managerAttributesMutex; - //mutex to manage whether memory nodes managed by this manager are being modified + //global mutex to manage whether memory nodes are being modified //concurrent modifications can occur as long as there is only one unique thread // that has allocated the memory //garbage collection or destruction of the manager require a unique lock // so that the memory can be traversed - Concurrency::ReadWriteMutex memoryModificationMutex; + //note that this is a global lock because nodes may be mixed among more than one + // EvaluableNodeManager and so garbage collection should not happening while memory is being modified + static Concurrency::ReadWriteMutex memoryModificationMutex; protected: #endif diff --git a/src/Amalgam/interpreter/InterpreterOpcodesEntityAccess.cpp b/src/Amalgam/interpreter/InterpreterOpcodesEntityAccess.cpp index f8acf356..5c70c90d 100644 --- a/src/Amalgam/interpreter/InterpreterOpcodesEntityAccess.cpp +++ b/src/Amalgam/interpreter/InterpreterOpcodesEntityAccess.cpp @@ -479,10 +479,8 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_CALL_ENTITY_and_CALL_ENTIT if(called_entity == nullptr) return EvaluableNodeReference::Null(); - EvaluableNodeManager *called_entity_enm = &called_entity->evaluableNodeManager; - //if have arguments, use them - EvaluableNodeReference call_stack = ConvertArgsToCallStack(args, called_entity_enm); + EvaluableNodeReference call_stack = ConvertArgsToCallStack(args, evaluableNodeManager); node_stack.PushEvaluableNode(call_stack); //current pointer to write listeners @@ -532,8 +530,8 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_CALL_ENTITY_and_CALL_ENTIT retval.SetNeedCycleCheck(true); //can't count on that due to things written in the write listener } - //ConvertArgsToCallStack always adds an outer list that is safe to free using called_entity_enm - called_entity_enm->FreeNode(call_stack); + //ConvertArgsToCallStack always adds an outer list that is safe to free + evaluableNodeManager->FreeNode(call_stack); if(_label_profiling_enabled) PerformanceProfiler::EndOperation(evaluableNodeManager->GetNumberOfUsedNodes()); @@ -593,26 +591,18 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_CALL_CONTAINER(EvaluableNo num_nodes_allowed_specified = true; } - //use the container's EvaluableNodeManager to make sure that an outer entity - // does not free a node that an inner entity is using, which can occur when the inner - // entity is calling its container and the container frees the node - EvaluableNodeManager *container_enm = &container->evaluableNodeManager; - //attempt to get arguments EvaluableNodeReference args = EvaluableNodeReference::Null(); if(ocn.size() > 1) - { args = InterpretNodeForImmediateUse(ocn[1]); - args = container_enm->DeepAllocCopy(args); - } //need to create arguments regardless - EvaluableNodeReference call_stack = ConvertArgsToCallStack(args, container_enm); + EvaluableNodeReference call_stack = ConvertArgsToCallStack(args, evaluableNodeManager); auto node_stack = CreateInterpreterNodeStackStateSaver(call_stack); //add accessing_entity to arguments. If accessing_entity already specified (it shouldn't be), let garbage collection clean it up - args->SetMappedChildNode(ENBISI_accessing_entity, container_enm->AllocNode(ENT_STRING, cur_entity_sid)); + args->SetMappedChildNode(ENBISI_accessing_entity, evaluableNodeManager->AllocNode(ENT_STRING, cur_entity_sid)); //compute execution limits if(AllowUnlimitedExecutionSteps() && (!num_steps_allowed_specified || num_steps_allowed == 0)) @@ -640,7 +630,7 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_CALL_CONTAINER(EvaluableNo #ifdef MULTITHREAD_SUPPORT &memoryModificationLock, &container.lock, #endif - container_label_name, this); + container_label_name, this, true); //accumulate costs of execution curExecutionStep += num_steps_executed;