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 3b094c3c..f182c538 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 @@ -39,8 +39,8 @@ to concrete positive/negative test evidence and current status. | G19-5.2.6 | Gate S-I MUST reject missing capability at load-time. | N/A | `BackendGateIIntegrationTest#gateI_rejectMissingCapability` | pass | | | G19-5.2.7 | Gate S-I MUST cover valid VM-owned intrinsic path. | `BackendGateIIntegrationTest#gateI_validIntrinsicPath` | N/A | pass | | | G19-5.2.8 | Gate S-I MUST cover repeatability across runtime line. | `RuntimeBackedCompatibilityAdapterTest#checkMustPassInStrictModeWhenRuntimeCommandIsValid` | `RuntimeBackedCompatibilityAdapterTest#checkMustFailInStrictModeWhenRuntimeCommandIsUnavailable` | partial | Runtime-line repeatability is validated at adapter contract level; multi-line matrix coverage is pending. | -| G20-6.2 | `IRVM_EXT` MUST declare structural metadata (`pops/pushes/is_branch/is_terminator`). | `IRVMOp` contract (`INTERNAL_EXT` shape) | N/A | partial | Needs dedicated extension-op fixtures beyond `INTERNAL_EXT`. | -| G20-6.3 | `IRVM_EXT` MUST be eliminable before bytecode emission. | `OptimizeIRVMServiceTest#optimizeDefaultPassesMustRemoveUnreachableInstructions` | `EmitBytecodePipelineStageTest#runMustFailWhenInternalOpcodesRemain`; `IRVMValidatorTest#validateMustRejectInternalOpcodeWhenConfigured` | pass | Emit stage blocks residual internal opcodes. | +| G20-6.2 | `IRVM_EXT` MUST declare structural metadata (`pops/pushes/is_branch/is_terminator`). | `IRVMValidatorTest#validateMustApplyStructuralMetadataForCustomInternalExtension`; `IRVMValidatorTest#validateMustRejectCustomInternalExtensionWhenStructuralMetadataUnderflowsStack`; `IRVMOp` record contract (`pops/pushes/branch/terminator/internal`) | N/A | pass | Dedicated extension fixtures now assert structural metadata is consumed by validation behavior. | +| G20-6.3 | `IRVM_EXT` MUST be eliminable before bytecode emission. | `OptimizeIRVMServiceTest#optimizeDefaultPassesMustEliminateUnreachableInternalExtensionBeforeEmission` | `EmitBytecodePipelineStageTest#runMustFailWhenInternalOpcodesRemain`; `EmitBytecodePipelineStageTest#runMustFailWhenInternalOpcodesRemainEvenWithNonEmptyEmissionPlan`; `IRVMValidatorTest#validateMustRejectInternalOpcodeWhenConfigured` | pass | Optimizer elimination path and emit-stage hard rejection path are both covered. | | G20-6.4 | IRVM MUST preserve per-function slot and identity headers. | `IRVMProgramTest#constructorMustRejectModuleAndEmissionPlanMismatch` | `IRVMProgramTest#constructorMustRejectModuleAndEmissionPlanMismatch` | pass | Header mismatch is rejected deterministically. | | G20-7.2 | Jump immediates MUST resolve to u32 function-relative offsets before emission. | `LowerToIRVMServiceTest#lowerMustResolveJumpTargetsFromLabels`; `BytecodeEmitterTest#emitMustEncodeJumpOpcodesWithU32Immediate` | `LowerToIRVMServiceTest#lowerMustRejectMissingJumpTargetLabel` | pass | | | G20-7.3 | Jump targets MUST be instruction-boundary valid. | N/A | `IRVMValidatorTest#validateMustRejectInvalidJumpTarget` | pass | | diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProgram.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProgram.java index 5519f363..600a1146 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProgram.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProgram.java @@ -85,6 +85,9 @@ public record IRVMProgram( private boolean coherentPair( final IRVMInstruction instruction, final BytecodeEmitter.Operation operation) { + if (instruction.op().internal()) { + return true; + } final var immediate = instruction.immediate() == null ? 0 : instruction.immediate(); return switch (instruction.op().opcode()) { case 0x01 -> operation.kind() == BytecodeEmitter.OperationKind.HALT; diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/IRVMValidatorTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/IRVMValidatorTest.java index 8dfc22e1..ef5d72f2 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/IRVMValidatorTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/IRVMValidatorTest.java @@ -5,6 +5,9 @@ import p.studio.compiler.backend.bytecode.BytecodeEmitter; import p.studio.compiler.backend.bytecode.BytecodeModule; import p.studio.utilities.structures.ReadOnlyList; +import java.util.Map; +import java.util.Set; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -125,6 +128,49 @@ class IRVMValidatorTest { assertDoesNotThrow(() -> validator.validate(module, false)); } + @Test + void validateMustApplyStructuralMetadataForCustomInternalExtension() { + final var extPush = new IRVMOp("IRVM_EXT_PUSH", 0xFFFE, 0, 0, 1, false, false, true); + final var customGate = new IRVMProfileFeatureGate(Map.of( + "experimental-structural-v1", Set.of(extPush, IRVMOp.HALT))); + final var customValidator = new IRVMValidator(customGate); + final var module = new IRVMModule( + "experimental-structural-v1", + ReadOnlyList.from(new IRVMFunction( + "main", + 0, + 0, + 0, + 1, + ReadOnlyList.from( + new IRVMInstruction(extPush, null), + new IRVMInstruction(IRVMOp.HALT, null))))); + + assertDoesNotThrow(() -> customValidator.validate(module, false)); + } + + @Test + void validateMustRejectCustomInternalExtensionWhenStructuralMetadataUnderflowsStack() { + final var extPop = new IRVMOp("IRVM_EXT_POP", 0xFFFD, 0, 1, 0, false, false, true); + final var customGate = new IRVMProfileFeatureGate(Map.of( + "experimental-structural-v1", Set.of(extPop, IRVMOp.HALT))); + final var customValidator = new IRVMValidator(customGate); + final var module = new IRVMModule( + "experimental-structural-v1", + ReadOnlyList.from(new IRVMFunction( + "main", + 0, + 0, + 0, + 1, + ReadOnlyList.from( + new IRVMInstruction(extPop, null), + new IRVMInstruction(IRVMOp.HALT, null))))); + + final var thrown = assertThrows(IRVMValidationException.class, () -> customValidator.validate(module, false)); + assertEquals(IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_STACK_UNDERFLOW, thrown.code()); + } + @Test void validateProgramMustRejectIntrinsicWithoutSignatureMetadata() { final var module = new IRVMModule( diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/OptimizeIRVMServiceTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/OptimizeIRVMServiceTest.java index 6a2614b0..0b9dad66 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/OptimizeIRVMServiceTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/OptimizeIRVMServiceTest.java @@ -2,12 +2,14 @@ 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.utilities.structures.ReadOnlyList; import java.util.ArrayList; 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.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -127,6 +129,51 @@ class OptimizeIRVMServiceTest { assertEquals(BytecodeEmitter.OperationKind.RET, optimized.emissionPlan().functions().getFirst().operations().getFirst().kind()); } + @Test + void optimizeDefaultPassesMustEliminateUnreachableInternalExtensionBeforeEmission() { + final var service = new OptimizeIRVMService(); + final var input = new IRVMProgram( + new IRVMModule( + "experimental-v1", + ReadOnlyList.from(new IRVMFunction( + "main", + 0, + 0, + 0, + 1, + ReadOnlyList.from( + new IRVMInstruction(IRVMOp.JMP, 8), + new IRVMInstruction(IRVMOp.INTERNAL_EXT, null), + new IRVMInstruction(IRVMOp.RET, null))))), + new BytecodeEmitter.EmissionPlan( + 0, + ReadOnlyList.empty(), + ReadOnlyList.empty(), + ReadOnlyList.from(new BytecodeEmitter.FunctionPlan( + "main", + 0, + 0, + 0, + 1, + ReadOnlyList.from( + BytecodeEmitter.Operation.jmp(8, null), + BytecodeEmitter.Operation.halt(), + BytecodeEmitter.Operation.ret()))))); + + final var optimized = service.optimize(input); + final var function = optimized.module().functions().getFirst(); + final var firstInstruction = function.instructions().getFirst(); + final var firstOperation = optimized.coherentEmissionPlan().functions().getFirst().operations().getFirst(); + + assertEquals(1, function.instructions().size()); + assertFalse(optimized.hasInternalOpcodes()); + assertEquals(IRVMOp.RET, firstInstruction.op()); + assertEquals(BytecodeEmitter.OperationKind.RET, firstOperation.kind()); + + final var emitted = new BytecodeEmitter().emit(optimized.coherentEmissionPlan()); + new BytecodePreloadVerifierService().verify(emitted); + } + private OptimizeIRVMService.IRVMPass namedPass( final String name, final List order, diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/workspaces/stages/EmitBytecodePipelineStageTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/workspaces/stages/EmitBytecodePipelineStageTest.java index 4298dc0a..76bcbfdd 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/workspaces/stages/EmitBytecodePipelineStageTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/workspaces/stages/EmitBytecodePipelineStageTest.java @@ -43,6 +43,43 @@ class EmitBytecodePipelineStageTest { assertEquals("MARSHAL_VERIFY_PRECHECK_INTERNAL_OPCODE_RESIDUAL", firstIssue.getCode()); } + @Test + void runMustFailWhenInternalOpcodesRemainEvenWithNonEmptyEmissionPlan() { + final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, ".")); + ctx.optimizedIrvm = new IRVMProgram( + new p.studio.compiler.backend.irvm.IRVMModule( + "experimental-v1", + ReadOnlyList.from(new p.studio.compiler.backend.irvm.IRVMFunction( + "main", + 0, + 0, + 0, + 1, + ReadOnlyList.from( + new p.studio.compiler.backend.irvm.IRVMInstruction(p.studio.compiler.backend.irvm.IRVMOp.INTERNAL_EXT, null), + new p.studio.compiler.backend.irvm.IRVMInstruction(p.studio.compiler.backend.irvm.IRVMOp.RET, null))))), + new BytecodeEmitter.EmissionPlan( + 0, + ReadOnlyList.empty(), + ReadOnlyList.empty(), + ReadOnlyList.from(new BytecodeEmitter.FunctionPlan( + "main", + 0, + 0, + 0, + 1, + ReadOnlyList.from( + BytecodeEmitter.Operation.halt(), + BytecodeEmitter.Operation.ret()))))); + final var stage = new EmitBytecodePipelineStage(); + + final var issues = stage.run(ctx, LogAggregator.empty()); + final var firstIssue = issues.asCollection().iterator().next(); + + assertTrue(issues.hasErrors()); + assertEquals("MARSHAL_VERIFY_PRECHECK_INTERNAL_OPCODE_RESIDUAL", firstIssue.getCode()); + } + @Test void runMustEmitBytecodeWhenPreconditionsAreSatisfied() { final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, "."));