Extract VM memory collection handler

This commit is contained in:
gamer147
2026-08-03 00:31:15 -04:00
parent 7712fb69ac
commit 5ae1c2a1bc
4 changed files with 280 additions and 223 deletions

View File

@@ -203,6 +203,9 @@ schedule construction, deadline/catch-up selection, and callback resumption opco
`engine/Age.Engine/Vm/VirtualMachine.Persistence.cs` owns catalog-unlock lookup, numbered save/load and nested `engine/Age.Engine/Vm/VirtualMachine.Persistence.cs` owns catalog-unlock lookup, numbered save/load and nested
restore continuation, metadata/copy/delete, thumbnail persistence, and shared-profile integer/string opcode restore continuation, metadata/copy/delete, thumbnail persistence, and shared-profile integer/string opcode
dispatch; capture/apply helpers and persistent coordinator state remain in `VirtualMachine.cs`. dispatch; capture/apply helpers and persistent coordinator state remain in `VirtualMachine.cs`.
`engine/Age.Engine/Vm/VirtualMachine.MemoryCollections.cs` owns string byte length, addressed lookup/copy,
inline arrays, rectangle search and stable index sorting, bounded integer queues/stacks, bit/range operations,
and native-style random-modulo dispatch; shared storage/address helpers remain in the coordinator.
The disposable `build/page-map-<SCENE>.jsonl` files are produced by editor/development Godot runs and map The disposable `build/page-map-<SCENE>.jsonl` files are produced by editor/development Godot runs and map
runtime ADV page ordinals to their authoritative script offsets for `tools/locate_page.py`. Packaged exports runtime ADV page ordinals to their authoritative script offsets for `tools/locate_page.py`. Packaged exports

View File

@@ -700,6 +700,13 @@ do not mix mechanical moves with semantic changes.
labels at their existing positions and routes them through guarded `StepPersistence`; capture/apply helpers labels at their existing positions and routes them through guarded `StepPersistence`; capture/apply helpers
and persistent coordinator state remain in `VirtualMachine.cs`. Runtime validation remains green. and persistent coordinator state remain in `VirtualMachine.cs`. Runtime validation remains green.
The thirteenth bounded `VirtualMachine.Step` extraction moved string byte length, addressed lookup/copy,
inline arrays, rectangle search and stable index sorting, bounded integer queues/stacks, bit/range operations,
and native-style random-modulo dispatch into
`engine/Age.Engine/Vm/VirtualMachine.MemoryCollections.cs`. The top-level dispatcher retains all labels and
aliases at their existing positions and routes them through guarded `StepMemoryCollection`; shared storage and
address-resolution helpers remain in the coordinator. Runtime validation remains green.
**Gate:** no externally visible behavior or command changes; generated artifacts are byte-identical where **Gate:** no externally visible behavior or command changes; generated artifacts are byte-identical where
deterministic, and the corresponding engine, Python, Godot, and corpus validations remain green after deterministic, and the corresponding engine, Python, Godot, and corpus validations remain green after
each domain move. each domain move.
@@ -1072,9 +1079,8 @@ layer's rendering diverges from ADV; save layout.
Continue step 2 of the **codebase consolidation** maintenance slice: behavior-neutral physical splits backed Continue step 2 of the **codebase consolidation** maintenance slice: behavior-neutral physical splits backed
by the tracked launcher and layered validation driver. With the planned `Main`, `GodotAdvHost`, and `GfxState` by the tracked launcher and layered validation driver. With the planned `Main`, `GodotAdvHost`, and `GfxState`
domains isolated and the audio, movie, surface/texture, retained-object, animation, presentation, ADV-text, domains isolated and the audio, movie, surface/texture, retained-object, animation, presentation, ADV-text,
text-history, ADV-service, input, and timing `VirtualMachine.Step` families routed through domain handlers, text-history, ADV-service, input, timing, persistence, and memory/collection `VirtualMachine.Step` families routed
with numbered-save/shared-profile persistence dispatch now isolated as well, extract the memory/collection utility through domain handlers, extract control-flow/coroutine dispatch next without replacing the proven dispatcher or
opcode family next without replacing the proven dispatcher or changing public types, commands, and generated changing public types, commands, and generated output.
output.
Concrete playthrough blockers may still preempt this bounded maintenance work; the consolidation effort does Concrete playthrough blockers may still preempt this bounded maintenance work; the consolidation effort does
not replace Phase B gameplay validation or the open cross-platform gates. not replace Phase B gameplay validation or the open cross-platform gates.

View File

@@ -0,0 +1,261 @@
using Age.Engine.Model;
namespace Age.Engine.Vm;
public sealed partial class VirtualMachine
{
private int StepMemoryCollection(string label, IReadOnlyList<Operand> a, int pc)
{
switch (label)
{
case "strlen": // 0x2c5: raw strlen(native encoded bytes)
Write(a[0], NativeStringByteLength(ReadStr(a[1])));
return pc + 1;
case "lookup-array":
LookupStore(a[0], BaseAddr(a[1]).Offset(Read(a[2]))); return pc + 1;
case "lookup-array-2d":
LookupStore(a[0], BaseAddr(a[1]).Offset(Read(a[2]) * Read(a[3]) + Read(a[4]))); return pc + 1;
case "take-address": // 0x63: pointer destination <- underlying address of operand 2
if (!TryStoreAddress(a[0], BaseAddr(a[1])))
{
HaltReason ??= $"take-address-destination-type:{a[0].Type}";
return HALT;
}
return pc + 1;
case "copy-inline-int-array": // 0x64: count dword followed by plain file values
{
int offset = checked((int)Read(a[1]));
if ((uint)offset >= (uint)_cur.Script.BodyDwords.Count)
{
HaltReason ??= $"inline-array-offset@0x{offset:x}";
return HALT;
}
uint rawCount = _cur.Script.BodyDwords[offset];
int available = _cur.Script.BodyDwords.Count - offset - 1;
if (rawCount > (uint)available)
{
HaltReason ??= $"inline-array-length@0x{offset:x}:{rawCount}";
return HALT;
}
int count = (int)rawCount;
for (int i = 0; i < count; i++)
WriteConsecutive(a[0], i, unchecked((int)_cur.Script.BodyDwords[offset + 1 + i]));
return pc + 1;
}
case "copy-dwords": // 0x1b0: memcpy(count * 4) across resolved integer-cell spans
{
int count = checked((int)Read(a[2]));
if (count < 0)
{
HaltReason ??= $"copy-dwords-negative-count:{count}";
return HALT;
}
VmAddress source = BaseAddr(a[0]);
VmAddress destination = BaseAddr(a[1]);
var values = new long[count];
for (int i = 0; i < count; i++)
values[i] = unchecked((int)ReadIntCell(source.Offset(i)));
for (int i = 0; i < count; i++) WriteIntCell(destination.Offset(i), values[i]);
return pc + 1;
}
case "find-hit-rectangle": // 0x12e: inclusive rectangle intersection over addressed arrays
case "u0041E940":
{
int previous = (int)Read(a[0]);
int count = System.Math.Max(0, (int)Read(a[7]));
long refLeft = ReadAddressedCell(a[1], 0);
long refRight = ReadAddressedCell(a[1], 1);
long refTop = ReadAddressedCell(a[1], 2);
long refBottom = ReadAddressedCell(a[1], 3);
int match = -1;
for (int index = previous + 1; index < count; index++)
{
long x = Read(a[2]) - ReadAddressedCell(a[5], index);
long y = Read(a[3]) - ReadAddressedCell(a[6], index);
long left = ReadAddressedCell(a[4], index * 4);
long right = ReadAddressedCell(a[4], index * 4 + 1);
long top = ReadAddressedCell(a[4], index * 4 + 2);
long bottom = ReadAddressedCell(a[4], index * 4 + 3);
bool isReferenceRectangle = AddressedCellIdentity(a[1], 0)
== AddressedCellIdentity(a[4], index * 4);
if (!isReferenceRectangle
&& x + refLeft <= right && x + refRight >= left
&& y + refTop <= bottom && y + refBottom >= top)
{
match = index;
break;
}
}
Write(a[0], match);
return pc + 1;
}
case "sort-indices-by-key-sum": // 0x12f: stable ascending permutation by signed key sum
case "u0041ECB0":
{
VmAddress output = BaseAddr(a[0]);
// Native writes element zero even when count is zero or negative, then builds the
// permutation in place with insertion sort. Read the count for each outer iteration:
// the handler fetches operand 4 repeatedly rather than caching it.
WriteIntCell(output, 0);
for (int sourceIndex = 1; sourceIndex < unchecked((int)Read(a[3])); sourceIndex++)
{
int position = sourceIndex;
while (position > 0)
{
int previousIndex = unchecked((int)ReadIntCell(output.Offset(position - 1)));
int previousKey = unchecked(
unchecked((int)ReadAddressedCell(a[1], previousIndex))
+ unchecked((int)ReadAddressedCell(a[2], previousIndex)));
int sourceKey = unchecked(
unchecked((int)ReadAddressedCell(a[1], sourceIndex))
+ unchecked((int)ReadAddressedCell(a[2], sourceIndex)));
if (sourceKey >= previousKey) break;
WriteIntCell(output.Offset(position), previousIndex);
position--;
}
WriteIntCell(output.Offset(position), sourceIndex);
}
return pc + 1;
}
case "u0041EF00":
case "reset-int-queue": // 0x132: 11 safe logical slots; native's admitted id 10 aliases stack 0
{
int queueId = unchecked((int)Read(a[0]));
if ((uint)queueId >= (uint)_intQueues.Length)
{
HaltReason ??= $"int-queue-id-out-of-range:{queueId}";
return HALT;
}
_intQueues[queueId] = new Queue<int>(0x100);
return pc + 1;
}
case "u0041EFF0":
case "enqueue-int": // 0x133 (queue_id, value)
{
int queueId = unchecked((int)Read(a[0]));
if ((uint)queueId >= (uint)_intQueues.Length)
{
HaltReason ??= $"int-queue-id-out-of-range:{queueId}";
return HALT;
}
if (_intQueues[queueId] is not { } queue)
{
HaltReason ??= $"int-queue-uninitialized:{queueId}";
return HALT;
}
queue.Enqueue(unchecked((int)Read(a[1])));
return pc + 1;
}
case "u0041F050":
case "try-dequeue-int": // 0x134 (queue_id, out_success, out_value)
{
int queueId = unchecked((int)Read(a[0]));
if ((uint)queueId >= (uint)_intQueues.Length)
{
HaltReason ??= $"int-queue-id-out-of-range:{queueId}";
return HALT;
}
if (_intQueues[queueId] is not { } queue)
{
HaltReason ??= $"int-queue-uninitialized:{queueId}";
return HALT;
}
if (queue.TryDequeue(out int value))
{
Write(a[1], 1);
Write(a[2], value);
}
else
{
// Native writes success=0 and an implementation pointer to operand 3. Shipped
// callers branch on success before reading it, so retain the prior destination.
Write(a[1], 0);
}
return pc + 1;
}
case "u0041F1C0":
case "reset-int-stack": // 0x137 (stack_id)
{
int stackId = unchecked((int)Read(a[0]));
if ((uint)stackId >= (uint)_intStacks.Length)
{
HaltReason ??= $"int-stack-id-out-of-range:{stackId}";
return HALT;
}
_intStacks[stackId] = new Stack<int>(0x100);
return pc + 1;
}
case "u0041F2B0":
case "push-int-stack": // 0x138 (stack_id, value)
{
int stackId = unchecked((int)Read(a[0]));
if ((uint)stackId >= (uint)_intStacks.Length)
{
HaltReason ??= $"int-stack-id-out-of-range:{stackId}";
return HALT;
}
_intStacks[stackId].Push(unchecked((int)Read(a[1])));
return pc + 1;
}
case "u0041F310":
case "try-pop-int-stack": // 0x139 (stack_id, out_success, out_value)
{
int stackId = unchecked((int)Read(a[0]));
if ((uint)stackId >= (uint)_intStacks.Length)
{
HaltReason ??= $"int-stack-id-out-of-range:{stackId}";
return HALT;
}
if (_intStacks[stackId].TryPop(out int value))
{
Write(a[1], 1);
Write(a[2], value);
}
else
{
// Native writes success=0 and leaks an internal EngineCtx pointer through
// operand 3. Preserve the destination instead of exposing host garbage.
Write(a[1], 0);
}
return pc + 1;
}
case "bit-set":
{
long bit = Read(a[1]);
if ((ulong)bit >= 32) { HaltReason ??= $"bit-index-out-of-range:{bit}"; return HALT; }
Write(a[0], Read(a[0]) | (1L << (int)bit)); return pc + 1;
}
case "bit-reset":
{
long bit = Read(a[1]);
if ((ulong)bit >= 32) { HaltReason ??= $"bit-index-out-of-range:{bit}"; return HALT; }
Write(a[0], Read(a[0]) & ~(1L << (int)bit)); return pc + 1;
}
case "check-bit": Write(a[0], (Read(a[1]) >> (int)(Read(a[2]) & 31)) & 1); return pc + 1;
case "zero-int-range":
case "copy-to-global": // pre-reference compatibility for opcode 0x6c
{
int count = System.Math.Max(0, checked((int)Read(a[1])));
for (int i = 0; i < count; i++) WriteConsecutive(a[0], i, 0);
return pc + 1;
}
case "random-modulo": // 0x60: native CRT rand() % bound
case "u0041A270":
{
long bound = Read(a[1]);
if (bound == 0)
{
Write(a[0], 0);
HaltReason ??= "random-modulo-zero";
return HALT;
}
Write(a[0], System.Random.Shared.Next(0x8000) % bound);
return pc + 1;
}
default:
throw new InvalidOperationException($"Non-memory/collection opcode routed to memory/collection handler: {label}");
}
}
}

View File

@@ -1311,252 +1311,39 @@ public sealed partial class VirtualMachine
case "store-shared-profile-string": // 0x1a9 case "store-shared-profile-string": // 0x1a9
case "load-shared-profile-string": // 0x1aa case "load-shared-profile-string": // 0x1aa
return StepPersistence(label, a, pc); return StepPersistence(label, a, pc);
case "strlen": // 0x2c5: raw strlen(native encoded bytes) case "strlen":
Write(a[0], NativeStringByteLength(ReadStr(a[1])));
return pc + 1;
case "lookup-array": case "lookup-array":
LookupStore(a[0], BaseAddr(a[1]).Offset(Read(a[2]))); return pc + 1;
case "lookup-array-2d": case "lookup-array-2d":
LookupStore(a[0], BaseAddr(a[1]).Offset(Read(a[2]) * Read(a[3]) + Read(a[4]))); return pc + 1;
case "take-address": // 0x63: pointer destination <- underlying address of operand 2 case "take-address": // 0x63: pointer destination <- underlying address of operand 2
if (!TryStoreAddress(a[0], BaseAddr(a[1])))
{
HaltReason ??= $"take-address-destination-type:{a[0].Type}";
return HALT;
}
return pc + 1;
case "copy-inline-int-array": // 0x64: count dword followed by plain file values case "copy-inline-int-array": // 0x64: count dword followed by plain file values
{
int offset = checked((int)Read(a[1]));
if ((uint)offset >= (uint)_cur.Script.BodyDwords.Count)
{
HaltReason ??= $"inline-array-offset@0x{offset:x}";
return HALT;
}
uint rawCount = _cur.Script.BodyDwords[offset];
int available = _cur.Script.BodyDwords.Count - offset - 1;
if (rawCount > (uint)available)
{
HaltReason ??= $"inline-array-length@0x{offset:x}:{rawCount}";
return HALT;
}
int count = (int)rawCount;
for (int i = 0; i < count; i++)
WriteConsecutive(a[0], i, unchecked((int)_cur.Script.BodyDwords[offset + 1 + i]));
return pc + 1;
}
case "copy-dwords": // 0x1b0: memcpy(count * 4) across resolved integer-cell spans case "copy-dwords": // 0x1b0: memcpy(count * 4) across resolved integer-cell spans
{ return StepMemoryCollection(label, a, pc);
int count = checked((int)Read(a[2]));
if (count < 0)
{
HaltReason ??= $"copy-dwords-negative-count:{count}";
return HALT;
}
VmAddress source = BaseAddr(a[0]);
VmAddress destination = BaseAddr(a[1]);
var values = new long[count];
for (int i = 0; i < count; i++)
values[i] = unchecked((int)ReadIntCell(source.Offset(i)));
for (int i = 0; i < count; i++) WriteIntCell(destination.Offset(i), values[i]);
return pc + 1;
}
case "find-hit-rectangle": // 0x12e: inclusive rectangle intersection over addressed arrays case "find-hit-rectangle": // 0x12e: inclusive rectangle intersection over addressed arrays
case "u0041E940": case "u0041E940":
{
int previous = (int)Read(a[0]);
int count = System.Math.Max(0, (int)Read(a[7]));
long refLeft = ReadAddressedCell(a[1], 0);
long refRight = ReadAddressedCell(a[1], 1);
long refTop = ReadAddressedCell(a[1], 2);
long refBottom = ReadAddressedCell(a[1], 3);
int match = -1;
for (int index = previous + 1; index < count; index++)
{
long x = Read(a[2]) - ReadAddressedCell(a[5], index);
long y = Read(a[3]) - ReadAddressedCell(a[6], index);
long left = ReadAddressedCell(a[4], index * 4);
long right = ReadAddressedCell(a[4], index * 4 + 1);
long top = ReadAddressedCell(a[4], index * 4 + 2);
long bottom = ReadAddressedCell(a[4], index * 4 + 3);
bool isReferenceRectangle = AddressedCellIdentity(a[1], 0)
== AddressedCellIdentity(a[4], index * 4);
if (!isReferenceRectangle
&& x + refLeft <= right && x + refRight >= left
&& y + refTop <= bottom && y + refBottom >= top)
{
match = index;
break;
}
}
Write(a[0], match);
return pc + 1;
}
case "sort-indices-by-key-sum": // 0x12f: stable ascending permutation by signed key sum case "sort-indices-by-key-sum": // 0x12f: stable ascending permutation by signed key sum
case "u0041ECB0": case "u0041ECB0":
{ return StepMemoryCollection(label, a, pc);
VmAddress output = BaseAddr(a[0]);
// Native writes element zero even when count is zero or negative, then builds the
// permutation in place with insertion sort. Read the count for each outer iteration:
// the handler fetches operand 4 repeatedly rather than caching it.
WriteIntCell(output, 0);
for (int sourceIndex = 1; sourceIndex < unchecked((int)Read(a[3])); sourceIndex++)
{
int position = sourceIndex;
while (position > 0)
{
int previousIndex = unchecked((int)ReadIntCell(output.Offset(position - 1)));
int previousKey = unchecked(
unchecked((int)ReadAddressedCell(a[1], previousIndex))
+ unchecked((int)ReadAddressedCell(a[2], previousIndex)));
int sourceKey = unchecked(
unchecked((int)ReadAddressedCell(a[1], sourceIndex))
+ unchecked((int)ReadAddressedCell(a[2], sourceIndex)));
if (sourceKey >= previousKey) break;
WriteIntCell(output.Offset(position), previousIndex);
position--;
}
WriteIntCell(output.Offset(position), sourceIndex);
}
return pc + 1;
}
case "u0041EF00": case "u0041EF00":
case "reset-int-queue": // 0x132: 11 safe logical slots; native's admitted id 10 aliases stack 0 case "reset-int-queue": // 0x132: 11 safe logical slots; native's admitted id 10 aliases stack 0
{
int queueId = unchecked((int)Read(a[0]));
if ((uint)queueId >= (uint)_intQueues.Length)
{
HaltReason ??= $"int-queue-id-out-of-range:{queueId}";
return HALT;
}
_intQueues[queueId] = new Queue<int>(0x100);
return pc + 1;
}
case "u0041EFF0": case "u0041EFF0":
case "enqueue-int": // 0x133 (queue_id, value) case "enqueue-int": // 0x133 (queue_id, value)
{
int queueId = unchecked((int)Read(a[0]));
if ((uint)queueId >= (uint)_intQueues.Length)
{
HaltReason ??= $"int-queue-id-out-of-range:{queueId}";
return HALT;
}
if (_intQueues[queueId] is not { } queue)
{
HaltReason ??= $"int-queue-uninitialized:{queueId}";
return HALT;
}
queue.Enqueue(unchecked((int)Read(a[1])));
return pc + 1;
}
case "u0041F050": case "u0041F050":
case "try-dequeue-int": // 0x134 (queue_id, out_success, out_value) case "try-dequeue-int": // 0x134 (queue_id, out_success, out_value)
{
int queueId = unchecked((int)Read(a[0]));
if ((uint)queueId >= (uint)_intQueues.Length)
{
HaltReason ??= $"int-queue-id-out-of-range:{queueId}";
return HALT;
}
if (_intQueues[queueId] is not { } queue)
{
HaltReason ??= $"int-queue-uninitialized:{queueId}";
return HALT;
}
if (queue.TryDequeue(out int value))
{
Write(a[1], 1);
Write(a[2], value);
}
else
{
// Native writes success=0 and an implementation pointer to operand 3. Shipped
// callers branch on success before reading it, so retain the prior destination.
Write(a[1], 0);
}
return pc + 1;
}
case "u0041F1C0": case "u0041F1C0":
case "reset-int-stack": // 0x137 (stack_id) case "reset-int-stack": // 0x137 (stack_id)
{
int stackId = unchecked((int)Read(a[0]));
if ((uint)stackId >= (uint)_intStacks.Length)
{
HaltReason ??= $"int-stack-id-out-of-range:{stackId}";
return HALT;
}
_intStacks[stackId] = new Stack<int>(0x100);
return pc + 1;
}
case "u0041F2B0": case "u0041F2B0":
case "push-int-stack": // 0x138 (stack_id, value) case "push-int-stack": // 0x138 (stack_id, value)
{
int stackId = unchecked((int)Read(a[0]));
if ((uint)stackId >= (uint)_intStacks.Length)
{
HaltReason ??= $"int-stack-id-out-of-range:{stackId}";
return HALT;
}
_intStacks[stackId].Push(unchecked((int)Read(a[1])));
return pc + 1;
}
case "u0041F310": case "u0041F310":
case "try-pop-int-stack": // 0x139 (stack_id, out_success, out_value) case "try-pop-int-stack": // 0x139 (stack_id, out_success, out_value)
{ return StepMemoryCollection(label, a, pc);
int stackId = unchecked((int)Read(a[0]));
if ((uint)stackId >= (uint)_intStacks.Length)
{
HaltReason ??= $"int-stack-id-out-of-range:{stackId}";
return HALT;
}
if (_intStacks[stackId].TryPop(out int value))
{
Write(a[1], 1);
Write(a[2], value);
}
else
{
// Native writes success=0 and leaks an internal EngineCtx pointer through
// operand 3. Preserve the destination instead of exposing host garbage.
Write(a[1], 0);
}
return pc + 1;
}
case "bit-set": case "bit-set":
{
long bit = Read(a[1]);
if ((ulong)bit >= 32) { HaltReason ??= $"bit-index-out-of-range:{bit}"; return HALT; }
Write(a[0], Read(a[0]) | (1L << (int)bit)); return pc + 1;
}
case "bit-reset": case "bit-reset":
{ case "check-bit":
long bit = Read(a[1]);
if ((ulong)bit >= 32) { HaltReason ??= $"bit-index-out-of-range:{bit}"; return HALT; }
Write(a[0], Read(a[0]) & ~(1L << (int)bit)); return pc + 1;
}
case "check-bit": Write(a[0], (Read(a[1]) >> (int)(Read(a[2]) & 31)) & 1); return pc + 1;
case "zero-int-range": case "zero-int-range":
case "copy-to-global": // pre-reference compatibility for opcode 0x6c case "copy-to-global": // pre-reference compatibility for opcode 0x6c
{
int count = System.Math.Max(0, checked((int)Read(a[1])));
for (int i = 0; i < count; i++) WriteConsecutive(a[0], i, 0);
return pc + 1;
}
case "random-modulo": // 0x60: native CRT rand() % bound case "random-modulo": // 0x60: native CRT rand() % bound
case "u0041A270": case "u0041A270":
{ return StepMemoryCollection(label, a, pc);
long bound = Read(a[1]);
if (bound == 0)
{
Write(a[0], 0);
HaltReason ??= "random-modulo-zero";
return HALT;
}
Write(a[0], System.Random.Shared.Next(0x8000) % bound);
return pc + 1;
}
case "jmp": return _cur.Script.IndexByOffset.GetValueOrDefault((int)a[0].Value, pc + 1); case "jmp": return _cur.Script.IndexByOffset.GetValueOrDefault((int)a[0].Value, pc + 1);
case "call": _cur.CallStack.Add(pc + 1); return _cur.Script.IndexByOffset.GetValueOrDefault((int)a[0].Value, pc + 1); case "call": _cur.CallStack.Add(pc + 1); return _cur.Script.IndexByOffset.GetValueOrDefault((int)a[0].Value, pc + 1);
case "ret": case "ret":