implements PR-06.2
This commit is contained in:
parent
0ec5693b0d
commit
bc98d5a3d4
@ -57,13 +57,13 @@ to concrete positive/negative test evidence and current status.
|
|||||||
| G20-11.3 | Source attribution MUST be preserved when source-actionable. | `LowerToIRVMPipelineStageTest#runMustAttachSourceAttributionForLoweringFailure`; `LowerToIRVMServiceTest#lowerMustMapHostAndIntrinsicCallsites` | `LowerToIRVMServiceTest#lowerMustRejectMissingCallee` | pass | Stage-level failure now carries explicit `file/start/end` attribution for lowering errors. |
|
| G20-11.3 | Source attribution MUST be preserved when source-actionable. | `LowerToIRVMPipelineStageTest#runMustAttachSourceAttributionForLoweringFailure`; `LowerToIRVMServiceTest#lowerMustMapHostAndIntrinsicCallsites` | `LowerToIRVMServiceTest#lowerMustRejectMissingCallee` | pass | Stage-level failure now carries explicit `file/start/end` attribution for lowering errors. |
|
||||||
| G21-5 | `OptimizeIRVM` MUST NOT be skipped in canonical pipeline order. | `BuilderPipelineServiceOrderTest#canonicalOrderMustContainOptimizeBetweenLowerAndEmit` | N/A | pass | Canonical stage order is enforced. |
|
| G21-5 | `OptimizeIRVM` MUST NOT be skipped in canonical pipeline order. | `BuilderPipelineServiceOrderTest#canonicalOrderMustContainOptimizeBetweenLowerAndEmit` | N/A | pass | Canonical stage order is enforced. |
|
||||||
| G21-6.1 | Optimize input MUST satisfy lowering obligations/profile/structural validity. | `OptimizeIRVMPipelineStageTest#runMustAcceptSupportedNonDefaultVmProfile` | `OptimizeIRVMPipelineStageTest#runMustRejectUnsupportedVmProfile` | pass | Input validation occurs before pass execution. |
|
| G21-6.1 | Optimize input MUST satisfy lowering obligations/profile/structural validity. | `OptimizeIRVMPipelineStageTest#runMustAcceptSupportedNonDefaultVmProfile` | `OptimizeIRVMPipelineStageTest#runMustRejectUnsupportedVmProfile` | pass | Input validation occurs before pass execution. |
|
||||||
| G21-6.2 | Optimize output MUST preserve semantics/contracts and remain emission-valid. | `OptimizeIRVMServiceTest#optimizeDefaultPassesMustRemoveUnreachableInstructions`; `EmitBytecodePipelineStageTest#runMustEmitBytecodeWhenPreconditionsAreSatisfied` | `OptimizeIRVMServiceTest#optimizeMustRejectPassThatMutatesVmProfile` | partial | Full semantic-equivalence fixture corpus still incremental. |
|
| G21-6.2 | Optimize output MUST preserve semantics/contracts and remain emission-valid. | `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForLoweredHostIntrinsicFixture`; `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForConditionalJoinFixture`; `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForSimpleLoopFixture`; `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForLinearCallFixture`; `EmitBytecodePipelineStageTest#runMustEmitBytecodeWhenPreconditionsAreSatisfied` | `OptimizeIRVMServiceTest#optimizeMustRejectPassThatMutatesVmProfile` | pass | Equivalence harness now validates on/off semantics and emission validity over CFG corpus with host/intrinsic paths. |
|
||||||
| G21-7.1 | Optimization passes MUST preserve observable semantics. | `OptimizeIRVMServiceTest#normalizeRedundantJumpTargetsPassMustCollapseJumpChain` | N/A | partial | Proof level is fixture-based, not formal equivalence. |
|
| G21-7.1 | Optimization passes MUST preserve observable semantics. | `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForConditionalJoinFixture`; `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForSimpleLoopFixture`; `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForLinearCallFixture` | N/A | pass | Observable trace equivalence is asserted across branching, loops, and call graphs. |
|
||||||
| G21-7.2 | Optimization passes MUST be deterministic for same input/profile. | `BackendSafetyGateSUTest#optimizeStageMustBeDeterministicForSameInputProgram` | N/A | pass | |
|
| G21-7.2 | Optimization passes MUST be deterministic for same input/profile. | `BackendSafetyGateSUTest#optimizeStageMustBeDeterministicForSameInputProgram` | N/A | pass | |
|
||||||
| G21-7.3 | Optimization passes MUST preserve profile compatibility. | `OptimizeIRVMPipelineStageTest#runMustAcceptSupportedNonDefaultVmProfile` | `OptimizeIRVMServiceTest#optimizeMustRejectPassThatMutatesVmProfile` | pass | |
|
| G21-7.3 | Optimization passes MUST preserve profile compatibility. | `OptimizeIRVMPipelineStageTest#runMustAcceptSupportedNonDefaultVmProfile` | `OptimizeIRVMServiceTest#optimizeMustRejectPassThatMutatesVmProfile` | pass | |
|
||||||
| G21-7.4 | Optimization passes MUST preserve host-vs-intrinsic boundary classification. | `LowerToIRVMServiceTest#lowerMustMapHostAndIntrinsicCallsites`; `BackendGateIIntegrationTest#gateI_validIntrinsicPath` | N/A | pass | No pass rewrites operation kind domains. |
|
| G21-7.4 | Optimization passes MUST preserve host-vs-intrinsic boundary classification. | `LowerToIRVMServiceTest#lowerMustMapHostAndIntrinsicCallsites`; `BackendGateIIntegrationTest#gateI_validIntrinsicPath` | N/A | pass | No pass rewrites operation kind domains. |
|
||||||
| G21-7.5 | Optimization passes MUST preserve diagnostics/source-attribution hooks. | `LowerToIRVMServiceTest#lowerMustMapHostAndIntrinsicCallsites` (spans on emission ops) | N/A | partial | Needs targeted optimizer regression asserting span retention after rewrites. |
|
| G21-7.5 | Optimization passes MUST preserve diagnostics/source-attribution hooks. | `LowerToIRVMServiceTest#lowerMustMapHostAndIntrinsicCallsites` (spans on emission ops) | N/A | partial | Needs targeted optimizer regression asserting span retention after rewrites. |
|
||||||
| G21-9.1 | Validation MUST include optimized-vs-non-optimized equivalence fixtures. | `OptimizeIRVMServiceTest` baseline pass fixtures | N/A | partial | Additional corpus planned for broader control-flow patterns. |
|
| G21-9.1 | Validation MUST include optimized-vs-non-optimized equivalence fixtures. | `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForLoweredHostIntrinsicFixture`; `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForConditionalJoinFixture`; `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForSimpleLoopFixture`; `OptimizeIRVMEquivalenceHarnessTest#optimizeOnOffMustPreserveObservableTraceForLinearCallFixture` | N/A | pass | Dedicated opt on/off harness with reusable interpreter and deterministic trace assertions is in place. |
|
||||||
| G21-9.2 | Validation MUST preserve known negative loader/verifier behavior. | `BackendSafetyGateSUTest#emitStageMustExposeMarshalingLinkageFailureDeterministically`; `BackendGateIIntegrationTest` rejection suite | N/A | pass | |
|
| G21-9.2 | Validation MUST preserve known negative loader/verifier behavior. | `BackendSafetyGateSUTest#emitStageMustExposeMarshalingLinkageFailureDeterministically`; `BackendGateIIntegrationTest` rejection suite | N/A | pass | |
|
||||||
| G21-9.3 | Validation MUST preserve deterministic artifact-level invariants. | `BackendSafetyGateSUTest#fullPipelineMustProduceDeterministicBytecodeForSameInput`; `BytecodeEmitterTest#emitMustRemainDeterministicAfterInterning` | N/A | pass | |
|
| G21-9.3 | Validation MUST preserve deterministic artifact-level invariants. | `BackendSafetyGateSUTest#fullPipelineMustProduceDeterministicBytecodeForSameInput`; `BytecodeEmitterTest#emitMustRemainDeterministicAfterInterning` | N/A | pass | |
|
||||||
| PBS13-12.0 | Executable backend handoff MUST satisfy addendum obligations. | `IRBackendExecutableContractTest` suite | N/A | pass | Row group `PBS13-12.x` details each obligation. |
|
| PBS13-12.0 | Executable backend handoff MUST satisfy addendum obligations. | `IRBackendExecutableContractTest` suite | N/A | pass | Row group `PBS13-12.x` details each obligation. |
|
||||||
|
|||||||
@ -0,0 +1,422 @@
|
|||||||
|
package p.studio.compiler.backend.irvm;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.compiler.backend.bytecode.BytecodeEmitter;
|
||||||
|
import p.studio.compiler.backend.bytecode.BytecodePreloadVerifierService;
|
||||||
|
import p.studio.compiler.backend.bytecode.BytecodeModule;
|
||||||
|
import p.studio.compiler.models.IRBackend;
|
||||||
|
import p.studio.compiler.models.IRBackendExecutableFunction;
|
||||||
|
import p.studio.compiler.source.Span;
|
||||||
|
import p.studio.compiler.source.identifiers.CallableId;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
import p.studio.compiler.source.identifiers.IntrinsicId;
|
||||||
|
import p.studio.compiler.source.tables.IntrinsicReference;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class OptimizeIRVMEquivalenceHarnessTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void optimizeOnOffMustPreserveObservableTraceForLoweredHostIntrinsicFixture() {
|
||||||
|
final var backend = IRBackend.builder()
|
||||||
|
.executableFunctions(ReadOnlyList.from(
|
||||||
|
fn("main", 1, ReadOnlyList.from(
|
||||||
|
callHost("gfx", "draw_pixel", 1, 0, 0),
|
||||||
|
callIntrinsic("core.color.pack", 1, 0, 0, 0),
|
||||||
|
ret()))))
|
||||||
|
.intrinsicPool(ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1)))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final var lowered = new LowerToIRVMService().lower(backend);
|
||||||
|
assertEquivalentObservableBehavior(lowered);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void optimizeOnOffMustPreserveObservableTraceForConditionalJoinFixture() {
|
||||||
|
final var program = singleFunctionProgram(
|
||||||
|
"main",
|
||||||
|
1,
|
||||||
|
ReadOnlyList.from(
|
||||||
|
new IRVMInstruction(IRVMOp.INTRINSIC, 901),
|
||||||
|
new IRVMInstruction(IRVMOp.JMP_IF_FALSE, 24),
|
||||||
|
new IRVMInstruction(IRVMOp.HOSTCALL, 0),
|
||||||
|
new IRVMInstruction(IRVMOp.JMP, 30),
|
||||||
|
new IRVMInstruction(IRVMOp.HOSTCALL, 0),
|
||||||
|
new IRVMInstruction(IRVMOp.RET, null)),
|
||||||
|
ReadOnlyList.from(
|
||||||
|
BytecodeEmitter.Operation.intrinsic(901, 0, 1, null),
|
||||||
|
BytecodeEmitter.Operation.jmpIfFalse(24, null),
|
||||||
|
hostcallOp("gfx", "draw_true_branch", 1, 0, 0),
|
||||||
|
BytecodeEmitter.Operation.jmp(30, null),
|
||||||
|
hostcallOp("gfx", "draw_false_branch", 1, 0, 0),
|
||||||
|
BytecodeEmitter.Operation.ret()));
|
||||||
|
assertEquivalentObservableBehavior(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void optimizeOnOffMustPreserveObservableTraceForSimpleLoopFixture() {
|
||||||
|
final var program = singleFunctionProgram(
|
||||||
|
"main",
|
||||||
|
1,
|
||||||
|
ReadOnlyList.from(
|
||||||
|
new IRVMInstruction(IRVMOp.INTRINSIC, 900),
|
||||||
|
new IRVMInstruction(IRVMOp.JMP_IF_FALSE, 24),
|
||||||
|
new IRVMInstruction(IRVMOp.HOSTCALL, 0),
|
||||||
|
new IRVMInstruction(IRVMOp.JMP, 0),
|
||||||
|
new IRVMInstruction(IRVMOp.RET, null)),
|
||||||
|
ReadOnlyList.from(
|
||||||
|
BytecodeEmitter.Operation.intrinsic(900, 0, 1, null),
|
||||||
|
BytecodeEmitter.Operation.jmpIfFalse(24, null),
|
||||||
|
hostcallOp("gfx", "loop_tick", 1, 0, 0),
|
||||||
|
BytecodeEmitter.Operation.jmp(0, null),
|
||||||
|
BytecodeEmitter.Operation.ret()));
|
||||||
|
assertEquivalentObservableBehavior(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void optimizeOnOffMustPreserveObservableTraceForLinearCallFixture() {
|
||||||
|
final var mainInstructions = ReadOnlyList.from(
|
||||||
|
new IRVMInstruction(IRVMOp.CALL, 1),
|
||||||
|
new IRVMInstruction(IRVMOp.HOSTCALL, 0),
|
||||||
|
new IRVMInstruction(IRVMOp.RET, null));
|
||||||
|
final var workerInstructions = ReadOnlyList.from(
|
||||||
|
new IRVMInstruction(IRVMOp.HOSTCALL, 0),
|
||||||
|
new IRVMInstruction(IRVMOp.RET, null));
|
||||||
|
final var mainOps = ReadOnlyList.from(
|
||||||
|
BytecodeEmitter.Operation.callFunc(1),
|
||||||
|
hostcallOp("gfx", "draw_main", 1, 0, 0),
|
||||||
|
BytecodeEmitter.Operation.ret());
|
||||||
|
final var workerOps = ReadOnlyList.from(
|
||||||
|
hostcallOp("gfx", "draw_worker", 1, 0, 0),
|
||||||
|
BytecodeEmitter.Operation.ret());
|
||||||
|
final var program = multiFunctionProgram(
|
||||||
|
ReadOnlyList.from(
|
||||||
|
irvmFunction("main", 0, mainInstructions),
|
||||||
|
irvmFunction("worker", 0, workerInstructions)),
|
||||||
|
ReadOnlyList.from(
|
||||||
|
functionPlan("main", 0, mainOps),
|
||||||
|
functionPlan("worker", 0, workerOps)));
|
||||||
|
|
||||||
|
assertEquivalentObservableBehavior(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertEquivalentObservableBehavior(final IRVMProgram input) {
|
||||||
|
final var optimizedOff = new OptimizeIRVMService(new IRVMValidator(), List.of()).optimize(input);
|
||||||
|
final var optimizedOn = new OptimizeIRVMService().optimize(input);
|
||||||
|
final var optimizedOnAgain = new OptimizeIRVMService().optimize(input);
|
||||||
|
|
||||||
|
final var offResult = execute(optimizedOff);
|
||||||
|
final var onResult = execute(optimizedOn);
|
||||||
|
assertEquals(offResult, onResult);
|
||||||
|
assertEquals(optimizedOn, optimizedOnAgain);
|
||||||
|
assertFalse(optimizedOn.hasInternalOpcodes());
|
||||||
|
assertFalse(optimizedOff.hasInternalOpcodes());
|
||||||
|
|
||||||
|
final var emitter = new BytecodeEmitter();
|
||||||
|
final var verifier = new BytecodePreloadVerifierService();
|
||||||
|
verifier.verify(emitter.emit(optimizedOff.coherentEmissionPlan()));
|
||||||
|
verifier.verify(emitter.emit(optimizedOn.coherentEmissionPlan()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExecutionResult execute(final IRVMProgram program) {
|
||||||
|
final var module = program.module();
|
||||||
|
if (module.functions().isEmpty()) {
|
||||||
|
return new ExecutionResult(ReadOnlyList.empty(), true, ReadOnlyList.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
final var plan = program.coherentEmissionPlan();
|
||||||
|
final var trace = new ArrayList<String>();
|
||||||
|
final var stack = new ArrayDeque<Integer>();
|
||||||
|
final var frames = new ArrayDeque<Frame>();
|
||||||
|
final var indexByPcByFunction = new ArrayList<HashMap<Integer, Integer>>(module.functions().size());
|
||||||
|
final var functionPlans = new ArrayList<BytecodeEmitter.FunctionPlan>(plan.functions().size());
|
||||||
|
final var intrinsicInvocationCount = new HashMap<Integer, Integer>();
|
||||||
|
for (final var function : module.functions()) {
|
||||||
|
indexByPcByFunction.add(indexByPc(function.instructions()));
|
||||||
|
}
|
||||||
|
for (final var functionPlan : plan.functions()) {
|
||||||
|
functionPlans.add(functionPlan);
|
||||||
|
}
|
||||||
|
frames.push(new Frame(0, 0));
|
||||||
|
|
||||||
|
var steps = 0;
|
||||||
|
while (!frames.isEmpty() && steps < 8_000) {
|
||||||
|
steps++;
|
||||||
|
final var frame = frames.peek();
|
||||||
|
final var function = module.functions().get(frame.functionIndex());
|
||||||
|
final var functionPlan = functionPlans.get(frame.functionIndex());
|
||||||
|
final var instructions = function.instructions();
|
||||||
|
if (frame.instructionIndex() < 0 || frame.instructionIndex() >= instructions.size()) {
|
||||||
|
throw new IllegalStateException("invalid pc index during interpretation");
|
||||||
|
}
|
||||||
|
final var instruction = instructions.get(frame.instructionIndex());
|
||||||
|
final var operation = functionPlan.operations().get(frame.instructionIndex());
|
||||||
|
final var op = instruction.op();
|
||||||
|
if (op == IRVMOp.HALT) {
|
||||||
|
trace.add("HALT");
|
||||||
|
return new ExecutionResult(ReadOnlyList.wrap(trace), true, stackSnapshot(stack));
|
||||||
|
} else if (op == IRVMOp.RET) {
|
||||||
|
trace.add("RET:" + function.name());
|
||||||
|
frames.pop();
|
||||||
|
if (frames.isEmpty()) {
|
||||||
|
trace.add("HALT");
|
||||||
|
return new ExecutionResult(ReadOnlyList.wrap(trace), true, stackSnapshot(stack));
|
||||||
|
}
|
||||||
|
} else if (op == IRVMOp.CALL) {
|
||||||
|
trace.add("CALL#" + instruction.immediate());
|
||||||
|
frame.advance();
|
||||||
|
frames.push(new Frame(instruction.immediate(), 0));
|
||||||
|
} else if (op == IRVMOp.JMP) {
|
||||||
|
frame.jump(indexByPcByFunction.get(frame.functionIndex()), instruction.immediate());
|
||||||
|
} else if (op == IRVMOp.JMP_IF_TRUE) {
|
||||||
|
final var value = stack.isEmpty() ? 0 : stack.pop();
|
||||||
|
if (value != 0) {
|
||||||
|
frame.jump(indexByPcByFunction.get(frame.functionIndex()), instruction.immediate());
|
||||||
|
} else {
|
||||||
|
frame.advance();
|
||||||
|
}
|
||||||
|
} else if (op == IRVMOp.JMP_IF_FALSE) {
|
||||||
|
final var value = stack.isEmpty() ? 0 : stack.pop();
|
||||||
|
if (value == 0) {
|
||||||
|
frame.jump(indexByPcByFunction.get(frame.functionIndex()), instruction.immediate());
|
||||||
|
} else {
|
||||||
|
frame.advance();
|
||||||
|
}
|
||||||
|
} else if (op == IRVMOp.HOSTCALL) {
|
||||||
|
final var hostDecl = operation.syscallDecl();
|
||||||
|
if (hostDecl != null) {
|
||||||
|
trace.add("HOSTCALL:" + hostDecl.module() + "." + hostDecl.name() + "@" + hostDecl.version());
|
||||||
|
} else {
|
||||||
|
trace.add("HOSTCALL");
|
||||||
|
}
|
||||||
|
applyOperationStackEffect(stack, operation);
|
||||||
|
frame.advance();
|
||||||
|
} else if (op == IRVMOp.INTRINSIC) {
|
||||||
|
trace.add("INTRINSIC#" + instruction.immediate());
|
||||||
|
applyOperationStackEffect(stack, operation);
|
||||||
|
final var pushes = operation.expectedRetSlots() == null ? 0 : operation.expectedRetSlots();
|
||||||
|
for (var i = 0; i < pushes; i++) {
|
||||||
|
final var count = intrinsicInvocationCount.getOrDefault(instruction.immediate(), 0);
|
||||||
|
intrinsicInvocationCount.put(instruction.immediate(), count + 1);
|
||||||
|
stack.push(simulatedIntrinsicValue(instruction.immediate(), count));
|
||||||
|
}
|
||||||
|
frame.advance();
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("unsupported op in equivalence harness: " + instruction.op().name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ExecutionResult(ReadOnlyList.wrap(trace), false, stackSnapshot(stack));
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashMap<Integer, Integer> indexByPc(final ReadOnlyList<IRVMInstruction> instructions) {
|
||||||
|
final var map = new HashMap<Integer, Integer>();
|
||||||
|
var pc = 0;
|
||||||
|
for (var i = 0; i < instructions.size(); i++) {
|
||||||
|
map.put(pc, i);
|
||||||
|
pc += instructions.get(i).encodedSize();
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyOperationStackEffect(
|
||||||
|
final ArrayDeque<Integer> stack,
|
||||||
|
final BytecodeEmitter.Operation operation) {
|
||||||
|
final var pops = operation.expectedArgSlots() == null ? 0 : operation.expectedArgSlots();
|
||||||
|
for (var i = 0; i < pops; i++) {
|
||||||
|
if (stack.isEmpty()) {
|
||||||
|
stack.push(0);
|
||||||
|
}
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int simulatedIntrinsicValue(
|
||||||
|
final int intrinsicId,
|
||||||
|
final int invocationIndex) {
|
||||||
|
if (intrinsicId == 900) {
|
||||||
|
return invocationIndex == 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
if (intrinsicId == 901) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReadOnlyList<Integer> stackSnapshot(final ArrayDeque<Integer> stack) {
|
||||||
|
return ReadOnlyList.wrap(new ArrayList<>(stack));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRVMProgram singleFunctionProgram(
|
||||||
|
final String name,
|
||||||
|
final int maxStackSlots,
|
||||||
|
final ReadOnlyList<IRVMInstruction> instructions,
|
||||||
|
final ReadOnlyList<BytecodeEmitter.Operation> operations) {
|
||||||
|
return multiFunctionProgram(
|
||||||
|
ReadOnlyList.from(irvmFunction(name, maxStackSlots, instructions)),
|
||||||
|
ReadOnlyList.from(functionPlan(name, maxStackSlots, operations)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRVMProgram multiFunctionProgram(
|
||||||
|
final ReadOnlyList<IRVMFunction> functions,
|
||||||
|
final ReadOnlyList<BytecodeEmitter.FunctionPlan> functionPlans) {
|
||||||
|
return new IRVMProgram(
|
||||||
|
new IRVMModule("core-v1", functions),
|
||||||
|
new BytecodeEmitter.EmissionPlan(
|
||||||
|
0,
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
functionPlans));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRVMFunction irvmFunction(
|
||||||
|
final String name,
|
||||||
|
final int maxStackSlots,
|
||||||
|
final ReadOnlyList<IRVMInstruction> instructions) {
|
||||||
|
return new IRVMFunction(
|
||||||
|
name,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
maxStackSlots,
|
||||||
|
instructions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BytecodeEmitter.FunctionPlan functionPlan(
|
||||||
|
final String name,
|
||||||
|
final int maxStackSlots,
|
||||||
|
final ReadOnlyList<BytecodeEmitter.Operation> operations) {
|
||||||
|
return new BytecodeEmitter.FunctionPlan(
|
||||||
|
name,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
maxStackSlots,
|
||||||
|
operations);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BytecodeEmitter.Operation hostcallOp(
|
||||||
|
final String module,
|
||||||
|
final String name,
|
||||||
|
final int version,
|
||||||
|
final int argSlots,
|
||||||
|
final int retSlots) {
|
||||||
|
return BytecodeEmitter.Operation.hostcall(
|
||||||
|
new BytecodeModule.SyscallDecl(module, name, version, argSlots, retSlots),
|
||||||
|
argSlots,
|
||||||
|
retSlots,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRBackendExecutableFunction fn(
|
||||||
|
final String name,
|
||||||
|
final int callableId,
|
||||||
|
final ReadOnlyList<IRBackendExecutableFunction.Instruction> instructions) {
|
||||||
|
return new IRBackendExecutableFunction(
|
||||||
|
new FileId(1),
|
||||||
|
"app",
|
||||||
|
name,
|
||||||
|
new CallableId(callableId),
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
instructions,
|
||||||
|
Span.none());
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRBackendExecutableFunction.Instruction ret() {
|
||||||
|
return new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.RET,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Span.none());
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRBackendExecutableFunction.Instruction callHost(
|
||||||
|
final String module,
|
||||||
|
final String name,
|
||||||
|
final long version,
|
||||||
|
final int argSlots,
|
||||||
|
final int retSlots) {
|
||||||
|
return new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.CALL_HOST,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
new IRBackendExecutableFunction.HostCallMetadata(module, name, version, argSlots, retSlots),
|
||||||
|
null,
|
||||||
|
Span.none());
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRBackendExecutableFunction.Instruction callIntrinsic(
|
||||||
|
final String canonicalName,
|
||||||
|
final long canonicalVersion,
|
||||||
|
final int intrinsicId,
|
||||||
|
final int argSlots,
|
||||||
|
final int retSlots) {
|
||||||
|
return new IRBackendExecutableFunction.Instruction(
|
||||||
|
IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
new IRBackendExecutableFunction.IntrinsicCallMetadata(canonicalName, canonicalVersion, new IntrinsicId(intrinsicId)),
|
||||||
|
argSlots,
|
||||||
|
retSlots,
|
||||||
|
Span.none());
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ExecutionResult(
|
||||||
|
ReadOnlyList<String> trace,
|
||||||
|
boolean halted,
|
||||||
|
ReadOnlyList<Integer> stack) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Frame {
|
||||||
|
private final int functionIndex;
|
||||||
|
private int instructionIndex;
|
||||||
|
|
||||||
|
private Frame(
|
||||||
|
final int functionIndex,
|
||||||
|
final int instructionIndex) {
|
||||||
|
this.functionIndex = functionIndex;
|
||||||
|
this.instructionIndex = instructionIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
int functionIndex() {
|
||||||
|
return functionIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
int instructionIndex() {
|
||||||
|
return instructionIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void advance() {
|
||||||
|
instructionIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void jump(
|
||||||
|
final HashMap<Integer, Integer> indexByPc,
|
||||||
|
final int targetPc) {
|
||||||
|
final var targetIndex = indexByPc.get(targetPc);
|
||||||
|
if (targetIndex == null) {
|
||||||
|
throw new IllegalStateException("invalid jump target for harness");
|
||||||
|
}
|
||||||
|
instructionIndex = targetIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user