Skip to content
Draft
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
49 changes: 15 additions & 34 deletions src/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ static char* formatStackContents(VMContext* ctx) {
}
#endif

#if IS_WAD17_OR_HIGHER_ENABLED
// Returns the native byte size of a GML data type on the runner's stack.
// The Dup instruction (and several BC17+ BREAK sub-opcodes) encode byte counts, not slot counts.
static int gmlTypeNativeSize(uint8_t gmlType) {
Expand All @@ -56,6 +57,7 @@ static int gmlTypeNativeSize(uint8_t gmlType) {
default: return 16;
}
}
#endif

static void stackPush(VMContext* ctx, RValue val) {
require(VM_STACK_SIZE > ctx->stack.top);
Expand Down Expand Up @@ -1832,6 +1834,7 @@ static void handleCmp(VMContext* ctx, uint32_t instr) {
stackPush(ctx,RValue_makeBool(result));
}

#if IS_WAD17_OR_HIGHER_ENABLED
// Converts a native byte count to RValue slot count by walking the stack backwards from a given position.
// Reads each slot's gmlStackType (set at push time) to compute its native footprint.
static int32_t bytesToSlotCount(VMContext* ctx, int32_t nativeBytes, int32_t stackPos) {
Expand All @@ -1846,13 +1849,15 @@ static int32_t bytesToSlotCount(VMContext* ctx, int32_t nativeBytes, int32_t sta
require(remaining == 0); // Byte count must align exactly to slot boundaries

@Fancy2209 Fancy2209 Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Chapter 4 Test Hits this now

return slots;
}
#endif

static void handleDup(VMContext* ctx, uint32_t instr) {
uint16_t operand = (uint16_t)(instr & 0xFFFF);

#if IS_WAD17_OR_HIGHER_ENABLED
uint8_t type1 = instrType1(instr);
int32_t typeSize = gmlTypeNativeSize(type1);

#if IS_WAD17_OR_HIGHER_ENABLED
// Swap mode (WAD17+): bit 15 of operand is set.
// The Dup instruction doubles as a stack rotation when bit 15 is set.
// It takes the top N items and moves them below the next M items.
Expand Down Expand Up @@ -1894,39 +1899,15 @@ static void handleDup(VMContext* ctx, uint32_t instr) {
}
#endif

// Normal dup mode: total bytes to duplicate = (operand + 1) * sizeof(type1)
// WAD17+ uses the low 15 bits of operand.
// WAD16 and below uses the low 15 bits of operand.
// Bit 15 is the swap-mode flag, handled above.
int32_t operandCount;
#if IS_WAD17_OR_HIGHER_ENABLED
operandCount = IS_WAD17_OR_HIGHER(ctx) ? (int32_t)(operand & 0x7FFF) : (int32_t)(operand & 0xFF);
#else
operandCount = (int32_t)(operand & 0xFF);
#endif
int32_t totalBytes = (operandCount + 1) * typeSize;
int32_t count = bytesToSlotCount(ctx, totalBytes, ctx->stack.top);

// Copy 'count' items from the top of the stack (preserving order)
int32_t startIdx = ctx->stack.top - count;
for (int32_t i = 0; count > i; i++) {
RValue copy = ctx->stack.slots[startIdx + i];

// If the value owns a string, duplicate it to avoid double-free.
// For arrays and methods, bump the refcount so each duplicate independently owns a reference.
if (copy.type == RVALUE_STRING && copy.ownsReference && copy.string != nullptr) {
copy.string = safeStrdup(copy.string);
} else if (copy.type == RVALUE_ARRAY && copy.ownsReference && copy.array != nullptr) {
GMLArray_incRef(copy.array);
#if IS_WAD17_OR_HIGHER_ENABLED
} else if (copy.type == RVALUE_METHOD && copy.ownsReference && copy.method != nullptr) {
GMLMethod_incRef(copy.method);
#endif
} else if (copy.type == RVALUE_STRUCT && copy.ownsReference && copy.structInst != nullptr) {
Instance_structIncRef(copy.structInst);
}

stackPush(ctx, copy);
// In the YoYo Runner, the type1 would've been used to figure out how many bytes to be copied from the stack
// Because all of our types are tagged RValues, we don't need to rely on it (yay)
// extra = how many additional elements will be copied from the stack, that is...
// If the stack is [a, b], and extra is 1, the result will be [a, b, a, b]
int32_t total = operand + 1;
int32_t dupBottom = ctx->stack.top - total;
repeat(total, i) {
RValue target = ctx->stack.slots[dupBottom + i];
stackPush(ctx, RValue_makeIndependent(target));
}
}

Expand Down
Loading