diff --git a/docs/specs/compiler/22. Backend Spec-to-Test Conformance Matrix.md b/docs/specs/compiler/22. Backend Spec-to-Test Conformance Matrix.md index 93dd64c8..b664a119 100644 --- a/docs/specs/compiler/22. Backend Spec-to-Test Conformance Matrix.md +++ b/docs/specs/compiler/22. Backend Spec-to-Test Conformance Matrix.md @@ -3,7 +3,7 @@ Status: Draft v1 (Traceability Baseline) Applies to: backend conformance traceability for `IRBackend -> IRVM -> OptimizeIRVM -> EmitBytecode` -Last Updated: 2026-03-09 +Last Updated: 2026-03-26 ## 1. Purpose @@ -45,7 +45,7 @@ to concrete positive/negative test evidence and current status. | 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 | | | G20-7.4 | Reachable fallthrough beyond function end MUST be rejected. | N/A | `LowerToIRVMServiceTest#lowerMustRejectUnterminatedFunction`; `IRVMValidatorTest#validateMustRejectRetShapeMismatch` | pass | | -| G20-8.1 | Qualified entrypoint function id MUST be 0. | `LowerToIRVMServiceTest#lowerMustAssignEntrypointIdZeroAndSortRemainingDeterministically`; `LowerToIRVMServiceTest#lowerMustUseQualifiedEntrypointIdentityWhenProvided` | `LowerToIRVMServiceTest#lowerMustRejectWhenEntrypointDeclarationIsMissing`; `LowerToIRVMServiceTest#lowerMustRejectMissingQualifiedEntrypointIdentity`; `LowerToIRVMServiceTest#lowerMustRejectWhenEntrypointIsAmbiguous` | pass | Entrypoint is resolved strictly by `EntrypointRef(entryPointModuleId, entryPointCallableName)`. | +| G20-8.1 | Qualified entrypoint function id MUST be 0. | `LowerToIRVMServiceTest#lowerMustAssignEntrypointIdZeroAndSortRemainingDeterministically`; `LowerToIRVMServiceTest#lowerMustUseQualifiedEntrypointIdentityWhenProvided`; `LowerToIRVMServiceTest#lowerMustPublishSyntheticWrapperAtFunctionZeroAndKeepFinalRetInWrapperPath` | `LowerToIRVMServiceTest#lowerMustRejectWhenEntrypointDeclarationIsMissing`; `LowerToIRVMServiceTest#lowerMustRejectMissingQualifiedEntrypointIdentity`; `LowerToIRVMServiceTest#lowerMustRejectWhenEntrypointIsAmbiguous`; `LowerToIRVMServiceTest#lowerMustRejectWhenPublishedWrapperWouldNotOccupyFunctionZero` | pass | Entrypoint is resolved strictly by qualified identity; PBS wrapper publication now has dedicated zero-index coverage. | | G20-8.2 | Remaining function ids MUST follow deterministic moduleId-only ordering. | `LowerToIRVMServiceTest#lowerMustAssignEntrypointIdZeroAndSortRemainingDeterministically`; `LowerToIRVMServiceTest#lowerMustUseModulePoolCanonicalIdentityForNonEntrypointOrdering` | N/A | pass | Ordering key is `(moduleId -> modulePool canonical key, callable_name, source_start)`. | | G20-8.3 | Same admitted input graph MUST produce identical function-id assignment. | `BackendSafetyGateSUTest#fullPipelineMustProduceDeterministicBytecodeForSameInput` | N/A | pass | Artifact determinism implies stable function mapping for fixed input. | | G20-9.3 | `CALL_INTRINSIC` MUST remain distinct from host-binding path and resolve ids from canonical registry artifact. | `LowerToIRVMServiceTest#lowerMustMapHostAndIntrinsicCallsites`; `BackendGateIIntegrationTest#gateI_validIntrinsicPath`; `IRVMIntrinsicRegistryParityTest#registryResourceMustExposeExpectedCanonicalEntries`; `IRVMIntrinsicRegistryParityTest#registryMustStayInSyncWithRuntimeBuiltinsTable` | `IRVMValidatorTest#validateProgramMustRejectIntrinsicWithoutSignatureMetadata` | pass | Distinct operation kinds and canonical registry parity checks are enforced. | @@ -66,7 +66,7 @@ to concrete positive/negative test evidence and current status. | 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. | +| PBS13-12.0 | Executable backend handoff MUST satisfy addendum obligations. | `IRBackendExecutableContractTest` suite; `PBSFrontendPhaseServiceTest#shouldSynthesizePositiveTopic19FixtureAcrossFileInitProjectInitAndFrame` | `LowerToIRVMServiceTest#lowerMustRejectWhenSyntheticWrapperEntrypointIsMissing`; `LowerToIRVMServiceTest#lowerMustRejectWhenHiddenBootGuardIsMissing`; `LowerToIRVMServiceTest#lowerMustRejectWhenSyntheticCallableOriginIsMissing` | pass | Row group `PBS13-12.x` details each obligation; topic 19 closure now includes lifecycle wrapper/guard/origin evidence. | | PBS13-12.1.1 | Callable identity MUST be preserved at handoff. | `IRBackendExecutableContractTest#aggregatorMustPreserveExecutableFunctionOrderDeterministically` | N/A | pass | | | PBS13-12.1.2 | Observable callable signature MUST be preserved at handoff. | `IRBackendExecutableContractTest#functionContractMustRejectInvalidSlotAndSpanBounds` | N/A | pass | | | PBS13-12.1.3 | Callable category MUST be preserved at handoff. | `PbsFrontendCompilerTest#shouldLowerExecutableFunctionsWithCallsiteCategories` | N/A | pass | | diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java index 19727bb4..ba5faa76 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java @@ -416,6 +416,10 @@ public final class PbsFrontendCompiler { return "__pbs.frame_wrapper$m" + normalizedModuleIndex(moduleId); } + public static String bootGuardGlobalName(final ModuleId moduleId) { + return "__pbs.boot_guard$m" + normalizedModuleIndex(moduleId); + } + private static int normalizedModuleIndex(final ModuleId moduleId) { final var normalized = moduleId == null ? ModuleId.none() : moduleId; return normalized.isNone() ? -1 : normalized.getIndex(); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java index b55ad93d..6c58b5d3 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java @@ -119,6 +119,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { private IRBackend synthesizePublishedLifecycle(final IRBackend baseBackend) { final var callableSignatures = new ArrayList<>(baseBackend.getCallableSignatures().asList()); final var executableFunctions = new ArrayList<>(baseBackend.getExecutableFunctions().asList()); + final var globals = new ArrayList<>(baseBackend.getGlobals().asList()); final var fileInitFunctionsByModule = new LinkedHashMap>(); final var projectInitAnchorsByModule = new LinkedHashMap(); final var wrapperNamesByModule = new LinkedHashMap(); @@ -151,8 +152,9 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { entryPointModuleId = wrapperNamesByModule.keySet().iterator().next(); entryPointCallableName = wrapperNamesByModule.get(entryPointModuleId); } else { - entryPointModuleId = baseBackend.getEntryPointModuleId(); - entryPointCallableName = baseBackend.getEntryPointCallableName(); + final var legacyEntrypoint = resolveLegacyEntrypoint(baseBackend); + entryPointModuleId = legacyEntrypoint.moduleId(); + entryPointCallableName = legacyEntrypoint.callableName(); } final var sortedModuleIds = new ArrayList(); @@ -281,6 +283,17 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { 1, ReadOnlyList.wrap(instructions), frameExecutable.span())); + + if (globals.stream().noneMatch(global -> + moduleIdsMatch(entryPointModuleId, global.moduleId()) + && PbsFrontendCompiler.bootGuardGlobalName(entryPointModuleId).equals(global.name()))) { + globals.add(new p.studio.compiler.models.IRGlobal( + frameExecutable.fileId(), + entryPointModuleId, + PbsFrontendCompiler.bootGuardGlobalName(entryPointModuleId), + "bool", + frameExecutable.span())); + } } return IRBackend.builder() @@ -288,7 +301,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { .entryPointModuleId(entryPointModuleId) .functions(baseBackend.getFunctions()) .syntheticFunctions(baseBackend.getSyntheticFunctions()) - .globals(baseBackend.getGlobals()) + .globals(ReadOnlyList.wrap(globals)) .executableFunctions(ReadOnlyList.wrap(executableFunctions)) .modulePool(baseBackend.getModulePool()) .callableSignatures(ReadOnlyList.wrap(callableSignatures)) @@ -384,6 +397,19 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { return (int) value; } + private LegacyEntrypoint resolveLegacyEntrypoint(final IRBackend backend) { + for (final var candidateName : List.of("frame", "main")) { + final var candidates = backend.getExecutableFunctions().stream() + .filter(function -> candidateName.equals(function.callableName())) + .filter(function -> function.moduleId() != null && !function.moduleId().isNone()) + .toList(); + if (candidates.size() == 1) { + return new LegacyEntrypoint(candidates.getFirst().moduleId(), candidateName); + } + } + return new LegacyEntrypoint(backend.getEntryPointModuleId(), backend.getEntryPointCallableName()); + } + private Set blockedModulesByDependency( final Set failedModuleIds, final Map> dependenciesByModule) { @@ -414,4 +440,9 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { ModuleId moduleId, p.studio.compiler.models.IRBackendFile irBackendFile) { } + + private record LegacyEntrypoint( + ModuleId moduleId, + String callableName) { + } } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java index adac6436..d225c0c4 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java @@ -308,6 +308,8 @@ class PBSFrontendPhaseServiceTest { assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString()); assertTrue(irBackend.getEntryPointCallableName().startsWith("__pbs.frame_wrapper$m")); + assertTrue(irBackend.getGlobals().stream().anyMatch(global -> + global.name().startsWith("__pbs.boot_guard$m"))); final var wrapper = irBackend.getExecutableFunctions().stream() .filter(function -> irBackend.getEntryPointCallableName().equals(function.callableName())) .findFirst() @@ -319,6 +321,73 @@ class PBSFrontendPhaseServiceTest { assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.RET, wrapper.instructions().get(3).kind()); } + @Test + void shouldSynthesizePositiveTopic19FixtureAcrossFileInitProjectInitAndFrame() throws IOException { + final var projectRoot = tempDir.resolve("project-topic-19-positive"); + final var sourceRoot = projectRoot.resolve("src"); + final var modulePath = sourceRoot.resolve("game"); + Files.createDirectories(modulePath); + + final var globalsSource = modulePath.resolve("globals.pbs"); + final var mainSource = modulePath.resolve("main.pbs"); + final var modBarrel = modulePath.resolve("mod.barrel"); + Files.writeString(globalsSource, """ + declare global SCORE: int = 0; + + [Init] + fn warmup() -> void { return; } + """); + Files.writeString(mainSource, """ + [Init] + fn boot() -> void { return; } + + [Frame] + fn frame() -> void { return; } + """); + Files.writeString(modBarrel, """ + pub fn warmup() -> void; + pub fn boot() -> void; + pub fn frame() -> void; + pub global SCORE; + """); + + final var projectTable = new ProjectTable(); + final var fileTable = new FileTable(1); + final var projectId = projectTable.register(ProjectDescriptor.builder() + .rootPath(projectRoot) + .name("core") + .version("1.0.0") + .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot))) + .build()); + + registerFile(projectId, projectRoot, globalsSource, fileTable); + registerFile(projectId, projectRoot, mainSource, fileTable); + registerFile(projectId, projectRoot, modBarrel, fileTable); + + final var ctx = new FrontendPhaseContext( + projectTable, + fileTable, + new BuildStack(ReadOnlyList.wrap(List.of(projectId)))); + final var diagnostics = DiagnosticSink.empty(); + + final var irBackend = new PBSFrontendPhaseService().compile( + ctx, + diagnostics, + LogAggregator.empty(), + BuildingIssueSink.empty()); + + assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString()); + assertTrue(irBackend.getEntryPointCallableName().startsWith("__pbs.frame_wrapper$m")); + assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function -> + function.callableName().startsWith("__pbs.file_init$f"))); + assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function -> + function.callableName().startsWith("__pbs.module_init$m"))); + assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function -> + function.callableName().startsWith("__pbs.project_init$m"))); + assertTrue(irBackend.getGlobals().stream().anyMatch(global -> "SCORE".equals(global.name()))); + assertTrue(irBackend.getGlobals().stream().anyMatch(global -> global.name().startsWith("__pbs.boot_guard$m"))); + } + @Test void shouldSkipIrLoweringForSourcesMarkedAsSdkInterface() throws IOException { final var projectRoot = tempDir.resolve("project-sdk-interface"); diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMLoweringErrorCode.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMLoweringErrorCode.java index 6bf3d11c..2e5764ca 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMLoweringErrorCode.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMLoweringErrorCode.java @@ -5,6 +5,10 @@ public enum IRVMLoweringErrorCode { LOWER_IRVM_ENTRYPOINT_DECLARATION_MISSING, LOWER_IRVM_ENTRYPOINT_MISSING, LOWER_IRVM_ENTRYPOINT_AMBIGUOUS, + LOWER_IRVM_SYNTHETIC_WRAPPER_ENTRYPOINT_MISSING, + LOWER_IRVM_PUBLISHED_ENTRYPOINT_NOT_ZERO, + LOWER_IRVM_HIDDEN_BOOT_GUARD_MISSING, + LOWER_IRVM_SYNTHETIC_ORIGIN_MISSING, LOWER_IRVM_MISSING_CALLEE, LOWER_IRVM_INVALID_INTRINSIC_ID, LOWER_IRVM_CALL_ARG_SLOTS_MISMATCH, diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java index 1f7795c0..ed821d7b 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java @@ -4,6 +4,7 @@ import p.studio.compiler.backend.bytecode.BytecodeEmitter; import p.studio.compiler.backend.bytecode.BytecodeModule; import p.studio.compiler.models.IRBackend; import p.studio.compiler.models.IRBackendExecutableFunction; +import p.studio.compiler.models.IRSyntheticCallableKind; import p.studio.compiler.source.Span; import p.studio.compiler.source.identifiers.CallableId; import p.studio.compiler.source.identifiers.ModuleId; @@ -38,6 +39,7 @@ public class LowerToIRVMService { "IRBackend has no executable functions"); } + validatePbsLifecycleStructure(backend); final var ordered = orderFunctions(backend); final var funcIdByCallableId = new HashMap(); for (var i = 0; i < ordered.size(); i++) { @@ -452,6 +454,14 @@ public class LowerToIRVMService { entryPointModuleId.getIndex())); } final var entrypoint = entrypoints.getFirst(); + final var publishedWrapper = resolvePublishedWrapper(backend); + if (publishedWrapper != null + && (!entryPointCallableName.equals(publishedWrapper.callableName()) + || !sameModuleIdentity(entryPointModuleId, publishedWrapper.moduleId()))) { + throw new IRVMLoweringException( + IRVMLoweringErrorCode.LOWER_IRVM_PUBLISHED_ENTRYPOINT_NOT_ZERO, + "published entrypoint is not function index 0 because entrypoint identity does not select the published wrapper"); + } final var ordered = new ArrayList(sorted.size()); ordered.add(entrypoint); for (final var fn : sorted) { @@ -462,6 +472,83 @@ public class LowerToIRVMService { return ReadOnlyList.wrap(ordered); } + private void validatePbsLifecycleStructure(final IRBackend backend) { + if (backend.getSyntheticFunctions().isEmpty()) { + return; + } + for (final var syntheticFunction : backend.getSyntheticFunctions()) { + if (syntheticFunction.origin() == null + || syntheticFunction.origin().fileId() == null + || syntheticFunction.origin().anchorCallableName().isBlank() + || syntheticFunction.origin().span() == null + || syntheticFunction.origin().span().isNone()) { + throw new IRVMLoweringException( + IRVMLoweringErrorCode.LOWER_IRVM_SYNTHETIC_ORIGIN_MISSING, + "synthetic callable origin missing for " + syntheticFunction.callableName()); + } + } + + final var publishedWrapper = resolvePublishedWrapper(backend); + if (publishedWrapper == null) { + return; + } + final var wrapperExecutablePresent = backend.getExecutableFunctions().stream() + .anyMatch(function -> publishedWrapper.callableName().equals(function.callableName()) + && sameModuleIdentity(publishedWrapper.moduleId(), function.moduleId())); + if (!wrapperExecutablePresent) { + throw new IRVMLoweringException( + IRVMLoweringErrorCode.LOWER_IRVM_SYNTHETIC_WRAPPER_ENTRYPOINT_MISSING, + "synthetic wrapper entrypoint missing: " + publishedWrapper.callableName()); + } + + final var expectedBootGuardName = bootGuardName(publishedWrapper.moduleId()); + final var bootGuardPresent = backend.getGlobals().stream() + .anyMatch(global -> expectedBootGuardName.equals(global.name()) + && sameModuleIdentity(publishedWrapper.moduleId(), global.moduleId())); + if (!bootGuardPresent) { + throw new IRVMLoweringException( + IRVMLoweringErrorCode.LOWER_IRVM_HIDDEN_BOOT_GUARD_MISSING, + "hidden boot guard is missing for moduleId=%d".formatted( + publishedWrapper.moduleId() == null || publishedWrapper.moduleId().isNone() + ? -1 + : publishedWrapper.moduleId().getIndex())); + } + } + + private p.studio.compiler.models.IRSyntheticFunction resolvePublishedWrapper(final IRBackend backend) { + p.studio.compiler.models.IRSyntheticFunction publishedWrapper = null; + for (final var syntheticFunction : backend.getSyntheticFunctions()) { + if (syntheticFunction.kind() != IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER) { + continue; + } + if (publishedWrapper != null) { + throw new IRVMLoweringException( + IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_AMBIGUOUS, + "multiple published frame wrappers found in executable backend"); + } + publishedWrapper = syntheticFunction; + } + return publishedWrapper; + } + + private String bootGuardName(final ModuleId moduleId) { + final var normalizedModuleId = moduleId == null ? ModuleId.none() : moduleId; + return "__pbs.boot_guard$m" + (normalizedModuleId.isNone() ? -1 : normalizedModuleId.getIndex()); + } + + private boolean sameModuleIdentity( + final ModuleId left, + final ModuleId right) { + final var normalizedLeft = left == null ? ModuleId.none() : left; + final var normalizedRight = right == null ? ModuleId.none() : right; + if (normalizedLeft.isNone() && normalizedRight.isNone()) { + return true; + } + return !normalizedLeft.isNone() + && !normalizedRight.isNone() + && normalizedLeft.getIndex() == normalizedRight.getIndex(); + } + private String moduleSortKey( final IRBackend backend, final ModuleId moduleId) { diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java index 1b5a5b96..125ae5c6 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java @@ -3,6 +3,10 @@ package p.studio.compiler.backend.irvm; import org.junit.jupiter.api.Test; import p.studio.compiler.models.IRBackend; import p.studio.compiler.models.IRBackendExecutableFunction; +import p.studio.compiler.models.IRGlobal; +import p.studio.compiler.models.IRSyntheticCallableKind; +import p.studio.compiler.models.IRSyntheticFunction; +import p.studio.compiler.models.IRSyntheticOrigin; import p.studio.compiler.source.Span; import p.studio.compiler.source.identifiers.CallableId; import p.studio.compiler.source.identifiers.FileId; @@ -62,6 +66,17 @@ class LowerToIRVMServiceTest { final var backend = IRBackend.builder() .entryPointCallableName("__pbs.frame_wrapper$m0") .entryPointModuleId(new ModuleId(0)) + .globals(ReadOnlyList.from(new IRGlobal( + new FileId(0), + new ModuleId(0), + "__pbs.boot_guard$m0", + "bool", + Span.none()))) + .syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction( + new ModuleId(0), + "__pbs.frame_wrapper$m0", + IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER, + new IRSyntheticOrigin(new FileId(0), "frame", new Span(new FileId(0), 1, 10))))) .executableFunctions(ReadOnlyList.from( fn("__pbs.file_init$f1", "app", 30, ReadOnlyList.from(ret())), fn("__pbs.module_init$m0", "app", 31, ReadOnlyList.from( @@ -95,6 +110,105 @@ class LowerToIRVMServiceTest { .op()); } + @Test + void lowerMustRejectWhenSyntheticWrapperEntrypointIsMissing() { + final var backend = IRBackend.builder() + .entryPointCallableName("__pbs.frame_wrapper$m0") + .entryPointModuleId(new ModuleId(0)) + .globals(ReadOnlyList.from(new IRGlobal( + new FileId(0), + new ModuleId(0), + "__pbs.boot_guard$m0", + "bool", + Span.none()))) + .syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction( + new ModuleId(0), + "__pbs.frame_wrapper$m0", + IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER, + new IRSyntheticOrigin(new FileId(0), "frame", new Span(new FileId(0), 1, 10))))) + .executableFunctions(ReadOnlyList.from( + fn("frame", "app", 20, ReadOnlyList.from(ret())))) + .build(); + + final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend)); + assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_SYNTHETIC_WRAPPER_ENTRYPOINT_MISSING, thrown.code()); + } + + @Test + void lowerMustRejectWhenPublishedWrapperWouldNotOccupyFunctionZero() { + final var backend = IRBackend.builder() + .entryPointCallableName("frame") + .entryPointModuleId(new ModuleId(0)) + .globals(ReadOnlyList.from(new IRGlobal( + new FileId(0), + new ModuleId(0), + "__pbs.boot_guard$m0", + "bool", + Span.none()))) + .syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction( + new ModuleId(0), + "__pbs.frame_wrapper$m0", + IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER, + new IRSyntheticOrigin(new FileId(0), "frame", new Span(new FileId(0), 1, 10))))) + .executableFunctions(ReadOnlyList.from( + fn("__pbs.frame_wrapper$m0", "app", 40, ReadOnlyList.from( + callFunc("app", "frame", 20), + ret())), + fn("frame", "app", 20, ReadOnlyList.from(ret())))) + .build(); + + final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend)); + assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_PUBLISHED_ENTRYPOINT_NOT_ZERO, thrown.code()); + } + + @Test + void lowerMustRejectWhenHiddenBootGuardIsMissing() { + final var backend = IRBackend.builder() + .entryPointCallableName("__pbs.frame_wrapper$m0") + .entryPointModuleId(new ModuleId(0)) + .syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction( + new ModuleId(0), + "__pbs.frame_wrapper$m0", + IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER, + new IRSyntheticOrigin(new FileId(0), "frame", new Span(new FileId(0), 1, 10))))) + .executableFunctions(ReadOnlyList.from( + fn("__pbs.frame_wrapper$m0", "app", 40, ReadOnlyList.from( + callFunc("app", "frame", 20), + ret())), + fn("frame", "app", 20, ReadOnlyList.from(ret())))) + .build(); + + final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend)); + assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_HIDDEN_BOOT_GUARD_MISSING, thrown.code()); + } + + @Test + void lowerMustRejectWhenSyntheticCallableOriginIsMissing() { + final var backend = IRBackend.builder() + .entryPointCallableName("__pbs.frame_wrapper$m0") + .entryPointModuleId(new ModuleId(0)) + .globals(ReadOnlyList.from(new IRGlobal( + new FileId(0), + new ModuleId(0), + "__pbs.boot_guard$m0", + "bool", + Span.none()))) + .syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction( + new ModuleId(0), + "__pbs.frame_wrapper$m0", + IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER, + new IRSyntheticOrigin(new FileId(0), "", Span.none())))) + .executableFunctions(ReadOnlyList.from( + fn("__pbs.frame_wrapper$m0", "app", 40, ReadOnlyList.from( + callFunc("app", "frame", 20), + ret())), + fn("frame", "app", 20, ReadOnlyList.from(ret())))) + .build(); + + final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend)); + assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_SYNTHETIC_ORIGIN_MISSING, thrown.code()); + } + @Test void lowerMustMapHostAndIntrinsicCallsites() { final var backend = IRBackend.builder() diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/BackendClaimScopeSpecTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/BackendClaimScopeSpecTest.java index c4109e29..a501c21a 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/BackendClaimScopeSpecTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/BackendClaimScopeSpecTest.java @@ -13,9 +13,9 @@ import static org.junit.jupiter.api.Assertions.fail; class BackendClaimScopeSpecTest { private static final String LOWERING_SPEC_RELATIVE_PATH = - "docs/compiler/general/specs/20. IRBackend to IRVM Lowering Specification.md"; + "docs/specs/compiler/20. IRBackend to IRVM Lowering Specification.md"; private static final String MATRIX_RELATIVE_PATH = - "docs/compiler/general/specs/22. Backend Spec-to-Test Conformance Matrix.md"; + "docs/specs/compiler/22. Backend Spec-to-Test Conformance Matrix.md"; private static final String DECISION_RELATIVE_PATH = "docs/compiler/pbs/decisions/SPAWN-YIELD v1 Claim Rescope Decision.md"; @@ -63,7 +63,7 @@ class BackendClaimScopeSpecTest { private Path locateRepoRoot() { var cursor = Path.of(System.getProperty("user.dir")).toAbsolutePath().normalize(); while (cursor != null) { - final var hasDocs = Files.isDirectory(cursor.resolve("docs/compiler/general/specs")); + final var hasDocs = Files.isDirectory(cursor.resolve("docs/specs/compiler")); final var hasCompiler = Files.isDirectory(cursor.resolve("prometeu-compiler")); if (hasDocs && hasCompiler) { return cursor; diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/BackendConformanceMatrixSpecTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/BackendConformanceMatrixSpecTest.java index 303acc8e..a1f0ee72 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/BackendConformanceMatrixSpecTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/BackendConformanceMatrixSpecTest.java @@ -14,7 +14,7 @@ import static org.junit.jupiter.api.Assertions.*; class BackendConformanceMatrixSpecTest { private static final String MATRIX_RELATIVE_PATH = - "docs/compiler/general/specs/22. Backend Spec-to-Test Conformance Matrix.md"; + "docs/specs/compiler/22. Backend Spec-to-Test Conformance Matrix.md"; private static final String MATRIX_HARD_GATE_ENV = "PROMETEU_MATRIX_HARD_GATE"; private static final String CHANGED_FILES_ENV = "PROMETEU_CHANGED_FILES"; private static final List HARD_GATE_PATH_PREFIXES = List.of( @@ -158,8 +158,9 @@ class BackendConformanceMatrixSpecTest { var cursor = Path.of(System.getProperty("user.dir")).toAbsolutePath().normalize(); while (cursor != null) { final var hasDocs = Files.isDirectory(cursor.resolve("docs/compiler/general/specs")); + final var hasCurrentDocs = Files.isDirectory(cursor.resolve("docs/specs/compiler")); final var hasCompiler = Files.isDirectory(cursor.resolve("prometeu-compiler")); - if (hasDocs && hasCompiler) { + if ((hasDocs || hasCurrentDocs) && hasCompiler) { return cursor; } cursor = cursor.getParent(); diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/NoModuleKeyRegressionSpecTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/NoModuleKeyRegressionSpecTest.java index bc28c2ae..6b2127b5 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/NoModuleKeyRegressionSpecTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/NoModuleKeyRegressionSpecTest.java @@ -62,7 +62,7 @@ class NoModuleKeyRegressionSpecTest { private Path locateRepoRoot() { var cursor = Path.of(System.getProperty("user.dir")).toAbsolutePath().normalize(); while (cursor != null) { - final var hasDocs = Files.isDirectory(cursor.resolve("docs/compiler/general/specs")); + final var hasDocs = Files.isDirectory(cursor.resolve("docs/specs/compiler")); final var hasCompiler = Files.isDirectory(cursor.resolve("prometeu-compiler")); if (hasDocs && hasCompiler) { return cursor;