Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/runner.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
58 changes: 45 additions & 13 deletions src/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand All @@ -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
Expand All @@ -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;
Expand All @@ -3776,16 +3792,32 @@ 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;

// Store arguments in scriptArgs (mirrors GMS 1.4's global argument stack).
// 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;
Expand Down Expand Up @@ -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;
Expand Down
9 changes: 8 additions & 1 deletion src/vm.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
Loading