From bc98d5a3d42a76cfcfbb2daac9c6a10451066559 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 9 Mar 2026 07:59:13 +0000 Subject: [PATCH] implements PR-06.2 --- ...Backend Spec-to-Test Conformance Matrix.md | 6 +- .../OptimizeIRVMEquivalenceHarnessTest.java | 422 ++++++++++++++++++ 2 files changed, 425 insertions(+), 3 deletions(-) create mode 100644 prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/OptimizeIRVMEquivalenceHarnessTest.java diff --git a/docs/general/specs/22. Backend Spec-to-Test Conformance Matrix.md b/docs/general/specs/22. Backend Spec-to-Test Conformance Matrix.md index b9162c0a..3b094c3c 100644 --- a/docs/general/specs/22. Backend Spec-to-Test Conformance Matrix.md +++ b/docs/general/specs/22. Backend Spec-to-Test Conformance Matrix.md @@ -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. | | 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.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-7.1 | Optimization passes MUST preserve observable semantics. | `OptimizeIRVMServiceTest#normalizeRedundantJumpTargetsPassMustCollapseJumpChain` | N/A | partial | Proof level is fixture-based, not formal equivalence. | +| 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. | `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.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.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.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. | diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/OptimizeIRVMEquivalenceHarnessTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/OptimizeIRVMEquivalenceHarnessTest.java new file mode 100644 index 00000000..f551fa7a --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/OptimizeIRVMEquivalenceHarnessTest.java @@ -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(); + final var stack = new ArrayDeque(); + final var frames = new ArrayDeque(); + final var indexByPcByFunction = new ArrayList>(module.functions().size()); + final var functionPlans = new ArrayList(plan.functions().size()); + final var intrinsicInvocationCount = new HashMap(); + 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 indexByPc(final ReadOnlyList instructions) { + final var map = new HashMap(); + 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 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 stackSnapshot(final ArrayDeque stack) { + return ReadOnlyList.wrap(new ArrayList<>(stack)); + } + + private IRVMProgram singleFunctionProgram( + final String name, + final int maxStackSlots, + final ReadOnlyList instructions, + final ReadOnlyList operations) { + return multiFunctionProgram( + ReadOnlyList.from(irvmFunction(name, maxStackSlots, instructions)), + ReadOnlyList.from(functionPlan(name, maxStackSlots, operations))); + } + + private IRVMProgram multiFunctionProgram( + final ReadOnlyList functions, + final ReadOnlyList 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 instructions) { + return new IRVMFunction( + name, + 0, + 0, + 0, + maxStackSlots, + instructions); + } + + private BytecodeEmitter.FunctionPlan functionPlan( + final String name, + final int maxStackSlots, + final ReadOnlyList 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 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 trace, + boolean halted, + ReadOnlyList 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 indexByPc, + final int targetPc) { + final var targetIndex = indexByPc.get(targetPc); + if (targetIndex == null) { + throw new IllegalStateException("invalid jump target for harness"); + } + instructionIndex = targetIndex; + } + } +}