diff --git a/src/runner.c b/src/runner.c index c8e2524bc..0b65bfd12 100644 --- a/src/runner.c +++ b/src/runner.c @@ -215,6 +215,7 @@ static void executeCode(Runner* runner, Instance* instance, int32_t codeId) { const char* savedCodeName = vm->currentCodeName; RValue* savedLocalVars = vm->localVars; uint32_t savedLocalVarCount = vm->localVarCount; + bool savedLocalVarsOnHeap = vm->localVarsOnHeap; IntIntHashMap* savedCodeLocalsSlotMap = vm->currentCodeLocalsSlotMap; int32_t savedCodeIndex = vm->currentCodeIndex; int32_t savedStackTop = vm->stack.top; @@ -244,6 +245,7 @@ static void executeCode(Runner* runner, Instance* instance, int32_t codeId) { vm->currentCodeName = savedCodeName; vm->localVars = savedLocalVars; vm->localVarCount = savedLocalVarCount; + vm->localVarsOnHeap = savedLocalVarsOnHeap; vm->currentCodeLocalsSlotMap = savedCodeLocalsSlotMap; vm->currentCodeIndex = savedCodeIndex; vm->stack.top = savedStackTop; diff --git a/src/vm.c b/src/vm.c index 36ab05e67..bf35cd11f 100644 --- a/src/vm.c +++ b/src/vm.c @@ -472,8 +472,9 @@ static uint32_t resolveLocalSlot(VMContext* ctx, int32_t varID) { if (slot >= ctx->localVarCount) { RValue* resizedLocalVars = (RValue *)safeCalloc(slot + 1, sizeof(RValue)); memcpy(resizedLocalVars, ctx->localVars, sizeof(RValue) * ctx->localVarCount); - free(ctx->localVars); + if (ctx->localVarsOnHeap) free(ctx->localVars); ctx->localVars = resizedLocalVars; + ctx->localVarsOnHeap = true; ctx->localVarCount = slot + 1; } return slot; @@ -648,9 +649,10 @@ void VM_writeToScriptArgs(VMContext* ctx, int32_t writeIndex, RValue val) { RValue* newScriptArgs = (RValue *)safeCalloc(writeIndex + 1, sizeof(RValue)); if (ctx->scriptArgCount > 0) { memcpy(newScriptArgs, ctx->scriptArgs, ctx->scriptArgCount * sizeof(RValue)); - free(ctx->scriptArgs); + if (ctx->scriptArgsOnHeap) free(ctx->scriptArgs); } ctx->scriptArgs = newScriptArgs; + ctx->scriptArgsOnHeap = true; ctx->scriptArgCount = writeIndex + 1; } RValue_free(&ctx->scriptArgs[writeIndex]); // no-op if we are writing to a resized array that was (originally) out of bounds @@ -3639,9 +3641,11 @@ void VM_reset(VMContext* ctx) { ctx->currentEventObjectIndex = -1; ctx->scriptArgs = nullptr; ctx->scriptArgCount = 0; + ctx->scriptArgsOnHeap = false; ctx->currentCodeName = nullptr; ctx->localVars = nullptr; ctx->localVarCount = 0; + ctx->localVarsOnHeap = false; ctx->currentCodeLocalsSlotMap = nullptr; ctx->actionRelativeFlag = false; @@ -3704,7 +3708,16 @@ RValue VM_executeCode(VMContext* ctx, int32_t codeIndex) { setCurrentCodeLocalsSlotMap(ctx); uint32_t localsCount = computeLocalsCount(ctx, code); - RValue* localVars = (RValue *)safeCalloc(localsCount, sizeof(RValue)); + RValue localVarsInline[VM_MAX_STACK_LOCALS]; + RValue* localVars; + if (localsCount <= VM_MAX_STACK_LOCALS) { + localVars = localVarsInline; + memset(localVars, 0, sizeof(RValue) * localsCount); + ctx->localVarsOnHeap = false; + } else { + localVars = (RValue *)safeCalloc(localsCount, sizeof(RValue)); + ctx->localVarsOnHeap = true; + } ctx->localVars = localVars; ctx->localVarCount = localsCount; @@ -3729,9 +3742,10 @@ RValue VM_executeCode(VMContext* ctx, int32_t codeIndex) { repeat(ctx->localVarCount, i) { RValue_free(&ctx->localVars[i]); } - free(ctx->localVars); + if (ctx->localVarsOnHeap) free(ctx->localVars); ctx->localVars = nullptr; ctx->localVarCount = 0; + ctx->localVarsOnHeap = false; // Reset all values in the stack (see issue #137) // Keep in mind that recent GameMaker versions do seem to emit Pop/Popz when exiting loops (example: when using a repeat + return) but older versions DO need it @@ -3754,11 +3768,13 @@ RValue VM_callCodeIndex(VMContext* ctx, int32_t codeIndex, RValue* args, int32_t frame.savedBytecodeBase = ctx->bytecodeBase; frame.savedLocals = ctx->localVars; frame.savedLocalsCount = ctx->localVarCount; + frame.savedLocalVarsOnHeap = ctx->localVarsOnHeap; frame.savedCodeName = ctx->currentCodeName; frame.savedSavearefBalance = ctx->savearefBalance; frame.savedCodeLocalsSlotMap = ctx->currentCodeLocalsSlotMap; frame.savedScriptArgs = ctx->scriptArgs; frame.savedScriptArgCount = ctx->scriptArgCount; + frame.savedScriptArgsOnHeap = ctx->scriptArgsOnHeap; frame.savedCurrentCodeIndex = ctx->currentCodeIndex; frame.parent = ctx->callStack; ctx->callStack = &frame; @@ -3776,7 +3792,16 @@ RValue VM_callCodeIndex(VMContext* ctx, int32_t codeIndex, RValue* args, int32_t setCurrentCodeLocalsSlotMap(ctx); uint32_t localsCount = computeLocalsCount(ctx, code); - RValue* localVars = (RValue *)safeCalloc(localsCount, sizeof(RValue)); + RValue* localVars; + RValue localVarsInline[VM_MAX_STACK_LOCALS]; + if (localsCount <= VM_MAX_STACK_LOCALS) { + localVars = localVarsInline; + memset(localVars, 0, sizeof(RValue) * localsCount); + ctx->localVarsOnHeap = false; + } else { + localVars = (RValue *)safeCalloc(localsCount, sizeof(RValue)); + ctx->localVarsOnHeap = true; + } ctx->localVars = localVars; ctx->localVarCount = localsCount; @@ -3784,8 +3809,15 @@ RValue VM_callCodeIndex(VMContext* ctx, int32_t codeIndex, RValue* args, int32_t // Callee takes an INDEPENDENT reference for strings (strdup) and arrays (incRef) so // the caller's original args remain valid and owner-tracked by the caller. RValue* scriptArgs = nullptr; + RValue scriptArgsInline[VM_MAX_STACK_ARGS]; if (argCount > 0 && args != nullptr) { - scriptArgs = (RValue *)safeCalloc(argCount, sizeof(RValue)); + if (argCount <= VM_MAX_STACK_ARGS) { + scriptArgs = scriptArgsInline; + ctx->scriptArgsOnHeap = false; + } else { + scriptArgs = (RValue *)safeCalloc(argCount, sizeof(RValue)); + ctx->scriptArgsOnHeap = true; + } repeat(argCount, argIdx) { RValue argCopy = RValue_makeIndependent(args[argIdx]); scriptArgs[argIdx] = argCopy; @@ -3821,23 +3853,23 @@ RValue VM_callCodeIndex(VMContext* ctx, int32_t codeIndex, RValue* args, int32_t repeat(ctx->localVarCount, i) { RValue_free(&ctx->localVars[i]); } - - free(ctx->localVars); + if (ctx->localVarsOnHeap) free(ctx->localVars); // Free callee script args { - repeat(ctx->scriptArgCount, i) { - RValue_free(&ctx->scriptArgs[i]); - } + repeat(ctx->scriptArgCount, i) { + RValue_free(&ctx->scriptArgs[i]); + } } - - free(ctx->scriptArgs); + if (ctx->scriptArgsOnHeap) free(ctx->scriptArgs); ctx->localVars = saved->savedLocals; ctx->localVarCount = saved->savedLocalsCount; + ctx->localVarsOnHeap = saved->savedLocalVarsOnHeap; ctx->currentCodeLocalsSlotMap = saved->savedCodeLocalsSlotMap; ctx->scriptArgs = saved->savedScriptArgs; ctx->scriptArgCount = saved->savedScriptArgCount; + ctx->scriptArgsOnHeap = saved->savedScriptArgsOnHeap; ctx->currentCodeName = saved->savedCodeName; ctx->currentCodeIndex = saved->savedCurrentCodeIndex; ctx->savearefBalance = saved->savedSavearefBalance; diff --git a/src/vm.h b/src/vm.h index 103239dd7..40e3ca530 100644 --- a/src/vm.h +++ b/src/vm.h @@ -111,9 +111,12 @@ #define BREAK_ISNULLISH (-10) // Pop value, push bool: is the value nullish (undefined / pointer_null)? #define BREAK_PUSHREF (-11) // Push an asset reference (or a script/function reference) encoded in the 32-bit operand -// Max amount of args a function call can have until the args are heap-alloced. +// Max amount of args a function or script call can have until the args are heap-alloced. #define VM_MAX_STACK_ARGS 16 +// Max amount of local variables a code entry can have until the vars are heap-alloced. +#define VM_MAX_STACK_LOCALS 64 + // ===[ Variable Types for V17 Array Access ]=== #define VARTYPE_ARRAYPUSHAF 0x10 // Push array reference (read context) #define VARTYPE_ARRAYPOPAF 0x90 // Push array reference (write context) @@ -133,11 +136,13 @@ typedef struct CallFrame { uint8_t* savedBytecodeBase; RValue* savedLocals; uint32_t savedLocalsCount; + bool savedLocalVarsOnHeap; const char* savedCodeName; int32_t savedSavearefBalance; IntIntHashMap* savedCodeLocalsSlotMap; RValue* savedScriptArgs; int32_t savedScriptArgCount; + bool savedScriptArgsOnHeap; int32_t savedCurrentCodeIndex; struct CallFrame* parent; } CallFrame; @@ -203,6 +208,7 @@ struct VMContext { uint32_t codeEnd; RValue* localVars; uint32_t localVarCount; + bool localVarsOnHeap; struct Instance* globalScopeInstance; // used when GLOB scripts are being executed, and used for the "global" reference struct Instance* currentInstance; struct Instance* otherInstance; // "other" instance for collision events @@ -220,6 +226,7 @@ struct VMContext { EnvFrame* envStack; // Environment stack for with-statements (PushEnv/PopEnv) RValue* scriptArgs; // Arguments passed to current script (nullptr for non-script code) int32_t scriptArgCount; // Number of arguments passed + bool scriptArgsOnHeap; int32_t selfId; int32_t otherId; // Current event context (set by Runner_executeEvent, -1 when not in an event)