--- linuxsampler/trunk/src/scriptvm/ScriptVM.cpp 2016/07/13 15:51:06 2942 +++ linuxsampler/trunk/src/scriptvm/ScriptVM.cpp 2017/01/05 18:00:52 3076 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 - 2016 Christian Schoenebeck + * Copyright (c) 2014 - 2017 Christian Schoenebeck * * http://www.linuxsampler.org * @@ -19,14 +19,53 @@ #define DEBUG_SCRIPTVM_CORE 0 +/** + * Maximum amount of VM instructions to be executed per ScriptVM::exec() call + * in case loops are involved, before the script got automatically suspended + * for a certain amount of time to avoid any RT instability issues. + * + * The following value takes a max. execution time of 300 microseconds as aimed + * target, assuming an execution time of approximately 5 microseconds per + * instruction this leads to the very approximate value set below. + */ +#define SCRIPTVM_MAX_INSTR_PER_CYCLE_SOFT 70 + +/** + * Absolute maximum amount of VM instructions to be executed per + * ScriptVM::exec() call (even if no loops are involved), before the script got + * automatically suspended for a certain amount of time to avoid any RT + * instability issues. + * + * A distinction between "soft" and "hard" limit is done here ATM because a + * script author typically expects that his script might be interrupted + * automatically if he is using while() loops, however he might not be + * prepared that his script might also be interrupted if no loop is involved + * (i.e. on very large scripts). + * + * The following value takes a max. execution time of 1000 microseconds as + * aimed target, assuming an execution time of approximately 5 microseconds per + * instruction this leads to the very approximate value set below. + */ +#define SCRIPTVM_MAX_INSTR_PER_CYCLE_HARD 210 + +/** + * In case either SCRIPTVM_MAX_INSTR_PER_CYCLE_SOFT or + * SCRIPTVM_MAX_INSTR_PER_CYCLE_HARD was exceeded when calling + * ScriptVM::exec() : the amount of microseconds the respective script + * execution instance should be automatically suspended by the VM. + */ +#define SCRIPT_VM_FORCE_SUSPENSION_MICROSECONDS 1000 + int InstrScript_parse(LinuxSampler::ParserContext*); namespace LinuxSampler { + #if DEBUG_SCRIPTVM_CORE static void _printIndents(int n) { for (int i = 0; i < n; ++i) printf(" "); fflush(stdout); } + #endif static int _requiredMaxStackSizeFor(Statement* statement, int depth = 0) { if (!statement) return 1; @@ -92,15 +131,22 @@ return max; } - ScriptVM::ScriptVM() : m_eventHandler(NULL), m_parserContext(NULL) { + ScriptVM::ScriptVM() : m_eventHandler(NULL), m_parserContext(NULL), m_autoSuspend(true) { m_fnMessage = new CoreVMFunction_message; m_fnExit = new CoreVMFunction_exit; m_fnWait = new CoreVMFunction_wait(this); m_fnAbs = new CoreVMFunction_abs; m_fnRandom = new CoreVMFunction_random; m_fnNumElements = new CoreVMFunction_num_elements; + m_fnInc = new CoreVMFunction_inc; + m_fnDec = new CoreVMFunction_dec; + m_fnInRange = new CoreVMFunction_in_range; m_varRealTimer = new CoreVMDynVar_NKSP_REAL_TIMER; m_varPerfTimer = new CoreVMDynVar_NKSP_PERF_TIMER; + m_fnShLeft = new CoreVMFunction_sh_left; + m_fnShRight = new CoreVMFunction_sh_right; + m_fnMin = new CoreVMFunction_min; + m_fnMax = new CoreVMFunction_max; } ScriptVM::~ScriptVM() { @@ -110,6 +156,13 @@ delete m_fnAbs; delete m_fnRandom; delete m_fnNumElements; + delete m_fnInc; + delete m_fnDec; + delete m_fnInRange; + delete m_fnShLeft; + delete m_fnShRight; + delete m_fnMin; + delete m_fnMax; delete m_varRealTimer; delete m_varPerfTimer; } @@ -209,6 +262,13 @@ else if (name == "abs") return m_fnAbs; else if (name == "random") return m_fnRandom; else if (name == "num_elements") return m_fnNumElements; + else if (name == "inc") return m_fnInc; + else if (name == "dec") return m_fnDec; + else if (name == "in_range") return m_fnInRange; + else if (name == "sh_left") return m_fnShLeft; + else if (name == "sh_right") return m_fnShRight; + else if (name == "min") return m_fnMin; + else if (name == "max") return m_fnMax; return NULL; } @@ -231,7 +291,14 @@ } std::map ScriptVM::builtInConstIntVariables() { - return std::map(); + std::map m; + + m["$NI_CB_TYPE_INIT"] = VM_EVENT_HANDLER_INIT; + m["$NI_CB_TYPE_NOTE"] = VM_EVENT_HANDLER_NOTE; + m["$NI_CB_TYPE_RELEASE"] = VM_EVENT_HANDLER_RELEASE; + m["$NI_CB_TYPE_CONTROLLER"] = VM_EVENT_HANDLER_CONTROLLER; + + return m; } VMEventHandler* ScriptVM::currentVMEventHandler() { @@ -247,6 +314,14 @@ return m_parserContext->execContext; } + void ScriptVM::setAutoSuspendEnabled(bool b) { + m_autoSuspend = b; + } + + bool ScriptVM::isAutoSuspendEnabled() const { + return m_autoSuspend; + } + VMExecStatus_t ScriptVM::exec(VMParserContext* parserContext, VMExecContext* execContex, VMEventHandler* handler) { m_parserContext = dynamic_cast(parserContext); if (!m_parserContext) { @@ -270,6 +345,7 @@ ctx->status = VM_EXEC_RUNNING; StmtFlags_t flags = STMT_SUCCESS; + int instructionsCounter = 0; int& frameIdx = ctx->stackFrame; if (frameIdx < 0) { // start condition ... @@ -346,9 +422,25 @@ ctx->pushStack( whileStmt->statements() ); + if (flags == STMT_SUCCESS && m_autoSuspend && + instructionsCounter > SCRIPTVM_MAX_INSTR_PER_CYCLE_SOFT) + { + flags = StmtFlags_t(STMT_SUSPEND_SIGNALLED); + ctx->suspendMicroseconds = SCRIPT_VM_FORCE_SUSPENSION_MICROSECONDS; + } } else ctx->popStack(); + break; } } + + if (flags == STMT_SUCCESS && m_autoSuspend && + instructionsCounter > SCRIPTVM_MAX_INSTR_PER_CYCLE_HARD) + { + flags = StmtFlags_t(STMT_SUSPEND_SIGNALLED); + ctx->suspendMicroseconds = SCRIPT_VM_FORCE_SUSPENSION_MICROSECONDS; + } + + ++instructionsCounter; } if (flags & STMT_SUSPEND_SIGNALLED) {