Extract VM control flow handler

This commit is contained in:
gamer147
2026-08-03 09:16:46 -04:00
parent 5ae1c2a1bc
commit 82749fc1ba
4 changed files with 107 additions and 62 deletions

View File

@@ -0,0 +1,91 @@
using Age.Engine.Diagnostics;
using Age.Engine.Model;
namespace Age.Engine.Vm;
public sealed partial class VirtualMachine
{
private int StepControlFlow(string label, Instruction ins, int pc)
{
int op = ins.Opcode;
var a = ins.Args;
switch (label)
{
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 "ret":
if (_cur.CallStack.Count > 0) { int r = _cur.CallStack[^1]; _cur.CallStack.RemoveAt(_cur.CallStack.Count - 1); return r; }
return FRAME_RETURN; // empty intra-call stack => return from the script frame
case "jcc":
{
long tgt = Read(a[0]) != 0 ? a[1].Value : a[2].Value;
return tgt == NoJump ? pc + 1 : _cur.Script.IndexByOffset.GetValueOrDefault((int)tgt, pc + 1);
}
case "begin-value-switch":
_valueSwitchTargets.Clear(); return pc + 1;
case "add-value-switch-case":
_valueSwitchTargets[FormatSwitchValue(a[0])] = checked((int)Read(a[1])); return pc + 1;
case "value-switch-jump":
{
int target = _valueSwitchTargets.TryGetValue(FormatSwitchValue(a[0]), out int matched)
? matched : checked((int)Read(a[1]));
return _cur.Script.IndexByOffset.GetValueOrDefault(target, pc + 1);
}
case "u0041ADB0":
case "coroutine-save-yield-handlers": // 0x7b: retain native handler metadata
_cur.CoroutineYieldHandlerA = (int)Read(a[0]);
_cur.CoroutineYieldHandlerB = (int)Read(a[1]);
return pc + 1;
case "u00414D50":
case "yield-adv-coroutine": // 0x199: A -> nested service -> B -> 0x7c resume
{
int? targetOffset;
if (!_cur.CoroutineYieldActive)
{
_cur.CoroutineResumePc = pc + 1;
_cur.CoroutineYieldActive = true;
_host.SetAdvPagePresentationSuspended(Gfx, true);
targetOffset = _cur.CoroutineYieldHandlerA;
}
else targetOffset = _cur.CoroutineYieldHandlerB;
return targetOffset is int offset
? _cur.Script.IndexByOffset.GetValueOrDefault(offset, pc + 1)
: pc + 1;
}
case "u00416A90":
case "coroutine-resume": // 0x7c: restore the PC saved by op 0x199
if (_cur.CoroutineResumePc is int resumePc)
{
_cur.CoroutineResumePc = null;
_cur.CoroutineYieldActive = false;
_host.SetAdvPagePresentationSuspended(Gfx, false);
return resumePc;
}
return pc + 1; // cold bounded scene-entry path
case "u0041F9C0":
case "coroutine-label-yield": // 0x140: bounded host model for LABEL/J only
{
if (!IsAdvLabeledYield(_cur.Script, ins))
{
if (_sink.TracingSteps) _sink.Emit(TraceEvent.Stub(op, pc));
return pc + 1;
}
if (!TryGetAdvYieldTerminal(pc, a[0], out long terminal))
{
HaltReason ??= $"coroutine-yield-pattern@0x{ins.Offset:x}";
return HALT;
}
int visits = _cur.CoroutineYieldVisits.GetValueOrDefault(pc);
_cur.CoroutineYieldVisits[pc] = visits + 1;
// First visit must enter setup even if out retained this same terminal from a prior scene.
// Every later visit returns the script-encoded terminal and exits the bounded loop.
Write(a[0], visits == 0 ? (terminal == 0 ? 1 : 0) : terminal);
return pc + 1;
}
default:
throw new InvalidOperationException($"Non-control-flow opcode routed to control-flow handler: {label}");
}
}
}

View File

@@ -1344,79 +1344,22 @@ public sealed partial class VirtualMachine
case "random-modulo": // 0x60: native CRT rand() % bound
case "u0041A270":
return StepMemoryCollection(label, a, pc);
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 "jmp":
case "call":
case "ret":
if (_cur.CallStack.Count > 0) { int r = _cur.CallStack[^1]; _cur.CallStack.RemoveAt(_cur.CallStack.Count - 1); return r; }
return FRAME_RETURN; // empty intra-call stack => return from the script frame
case "jcc":
{
long tgt = Read(a[0]) != 0 ? a[1].Value : a[2].Value;
return tgt == NoJump ? pc + 1 : _cur.Script.IndexByOffset.GetValueOrDefault((int)tgt, pc + 1);
}
case "begin-value-switch":
_valueSwitchTargets.Clear(); return pc + 1;
case "add-value-switch-case":
_valueSwitchTargets[FormatSwitchValue(a[0])] = checked((int)Read(a[1])); return pc + 1;
case "value-switch-jump":
{
int target = _valueSwitchTargets.TryGetValue(FormatSwitchValue(a[0]), out int matched)
? matched : checked((int)Read(a[1]));
return _cur.Script.IndexByOffset.GetValueOrDefault(target, pc + 1);
}
case "u0041ADB0":
case "coroutine-save-yield-handlers": // 0x7b: retain native handler metadata
_cur.CoroutineYieldHandlerA = (int)Read(a[0]);
_cur.CoroutineYieldHandlerB = (int)Read(a[1]);
return pc + 1;
case "u00414D50":
case "yield-adv-coroutine": // 0x199: A -> nested service -> B -> 0x7c resume
{
int? targetOffset;
if (!_cur.CoroutineYieldActive)
{
_cur.CoroutineResumePc = pc + 1;
_cur.CoroutineYieldActive = true;
_host.SetAdvPagePresentationSuspended(Gfx, true);
targetOffset = _cur.CoroutineYieldHandlerA;
}
else targetOffset = _cur.CoroutineYieldHandlerB;
return targetOffset is int offset
? _cur.Script.IndexByOffset.GetValueOrDefault(offset, pc + 1)
: pc + 1;
}
case "u00416A90":
case "coroutine-resume": // 0x7c: restore the PC saved by op 0x199
if (_cur.CoroutineResumePc is int resumePc)
{
_cur.CoroutineResumePc = null;
_cur.CoroutineYieldActive = false;
_host.SetAdvPagePresentationSuspended(Gfx, false);
return resumePc;
}
return pc + 1; // cold bounded scene-entry path
case "u0041F9C0":
case "coroutine-label-yield": // 0x140: bounded host model for LABEL/J only
{
if (!IsAdvLabeledYield(_cur.Script, ins))
{
if (_sink.TracingSteps) _sink.Emit(TraceEvent.Stub(op, pc));
return pc + 1;
}
if (!TryGetAdvYieldTerminal(pc, a[0], out long terminal))
{
HaltReason ??= $"coroutine-yield-pattern@0x{ins.Offset:x}";
return HALT;
}
int visits = _cur.CoroutineYieldVisits.GetValueOrDefault(pc);
_cur.CoroutineYieldVisits[pc] = visits + 1;
// First visit must enter setup even if out retained this same terminal from a prior scene.
// Every later visit returns the script-encoded terminal and exits the bounded loop.
Write(a[0], visits == 0 ? (terminal == 0 ? 1 : 0) : terminal);
return pc + 1;
}
return StepControlFlow(label, ins, pc);
case "throw-exit-request":
if (_o.IgnoreExitRequests) return pc + 1;
// Native op 0x1 throws Command_Exit_Exception through callbacks and nested script