Extract VM control flow handler
This commit is contained in:
@@ -206,6 +206,9 @@ dispatch; capture/apply helpers and persistent coordinator state remain in `Virt
|
||||
`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.
|
||||
`engine/Age.Engine/Vm/VirtualMachine.ControlFlow.cs` owns local jumps/calls/returns, value-switch construction,
|
||||
ADV coroutine handler save/yield/resume, and bounded labeled-yield dispatch; process/root exit and cross-script
|
||||
lifecycle remain in `VirtualMachine.cs`.
|
||||
|
||||
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
|
||||
|
||||
@@ -707,6 +707,13 @@ do not mix mechanical moves with semantic changes.
|
||||
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.
|
||||
|
||||
The fourteenth bounded `VirtualMachine.Step` extraction moved local jumps/calls/returns, value-switch
|
||||
construction, ADV coroutine handler save/yield/resume, and bounded labeled-yield dispatch into
|
||||
`engine/Age.Engine/Vm/VirtualMachine.ControlFlow.cs`. The top-level dispatcher retains all labels and aliases
|
||||
at their existing positions and routes them through guarded `StepControlFlow`; the complete instruction remains
|
||||
available for labeled-yield opcode/offset diagnostics. Process/root exit and cross-script lifecycle remain in
|
||||
the coordinator. Runtime validation remains green.
|
||||
|
||||
**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
|
||||
each domain move.
|
||||
@@ -1080,7 +1087,8 @@ Continue step 2 of the **codebase consolidation** maintenance slice: behavior-ne
|
||||
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,
|
||||
text-history, ADV-service, input, timing, persistence, and memory/collection `VirtualMachine.Step` families routed
|
||||
through domain handlers, extract control-flow/coroutine dispatch next without replacing the proven dispatcher or
|
||||
changing public types, commands, and generated output.
|
||||
through domain handlers, with control-flow/coroutine dispatch now isolated as well, extract process/root-exit and
|
||||
cross-script lifecycle dispatch next without replacing the proven dispatcher or changing public types, commands,
|
||||
and generated output.
|
||||
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.
|
||||
|
||||
91
engine/Age.Engine/Vm/VirtualMachine.ControlFlow.cs
Normal file
91
engine/Age.Engine/Vm/VirtualMachine.ControlFlow.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user