From 58c5ab26d7990cf3e78ce2cb20c4e6ead9054bc1 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Thu, 26 Mar 2026 19:38:58 +0000 Subject: [PATCH] implements PR-19.8 published wrapper entrypoint lowering --- .../p/studio/compiler/PBSDefinitions.java | 1 - .../compiler/pbs/PbsFrontendCompiler.java | 211 +++++++++++-- .../services/PBSFrontendPhaseService.java | 283 +++++++++++++++++- .../services/PBSFrontendPhaseServiceTest.java | 63 +++- .../backend/irvm/LowerToIRVMServiceTest.java | 38 +++ 5 files changed, 554 insertions(+), 42 deletions(-) diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/PBSDefinitions.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/PBSDefinitions.java index 45b08303..e041973a 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/PBSDefinitions.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/PBSDefinitions.java @@ -11,7 +11,6 @@ public class PBSDefinitions { .languageId("pbs") .allowedExtensions(ReadOnlySet.from("pbs", "barrel")) .sourceRoots(ReadOnlySet.from("src")) - .entryPointCallableName("frame") .stdlibVersions(List.of(FrontendSpec.Stdlib.asDefault(1))) .build(); } 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 3e73e4f3..19727bb4 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 @@ -1,6 +1,7 @@ package p.studio.compiler.pbs; import p.studio.compiler.messages.HostAdmissionContext; +import p.studio.compiler.models.IRBackendExecutableFunction; import p.studio.compiler.models.IRBackendFile; import p.studio.compiler.models.IRFunction; import p.studio.compiler.models.IRGlobal; @@ -16,9 +17,12 @@ import p.studio.compiler.pbs.parser.PbsParser; import p.studio.compiler.pbs.semantics.PbsDeclarationSemanticsValidator; import p.studio.compiler.pbs.semantics.PbsFlowSemanticsValidator; import p.studio.compiler.pbs.semantics.PbsLifecycleSemanticsValidator; +import p.studio.compiler.source.Span; import p.studio.compiler.source.diagnostics.DiagnosticSink; +import p.studio.compiler.source.identifiers.CallableId; import p.studio.compiler.source.identifiers.FileId; import p.studio.compiler.source.identifiers.ModuleId; +import p.studio.compiler.source.tables.CallableSignatureRef; import p.studio.compiler.source.tables.ModuleReference; import p.studio.compiler.source.tables.NameTable; import p.studio.utilities.structures.ReadOnlyList; @@ -172,15 +176,27 @@ public final class PbsFrontendCompiler { if (diagnostics.errorCount() > loweringErrorBaseline) { return IRBackendFile.empty(fileId); } + final var lifecycleArtifacts = scanLifecycleArtifacts(ast); + final var executableFunctions = new ArrayList<>(executableLowering.executableFunctions().asList()); + final var callableSignatures = new ArrayList<>(executableLowering.callableSignatures().asList()); + final var fileInitExecutable = lowerFileInitExecutable( + fileId, + effectiveModuleId, + lifecycleArtifacts, + executableFunctions, + callableSignatures); + if (fileInitExecutable != null) { + executableFunctions.add(fileInitExecutable); + } return new IRBackendFile( fileId, functions, syntheticFunctions, globals, - executableLowering.executableFunctions(), + ReadOnlyList.wrap(executableFunctions), reservedMetadata, effectiveModulePool, - executableLowering.callableSignatures(), + ReadOnlyList.wrap(callableSignatures), executableLowering.intrinsicPool()); } @@ -246,6 +262,125 @@ public final class PbsFrontendCompiler { final ModuleId moduleId, final PbsAst.File ast) { final var syntheticFunctions = new ArrayList(); + final var lifecycleArtifacts = scanLifecycleArtifacts(ast); + final var initDecl = lifecycleArtifacts.initDecl(); + final var frameDecl = lifecycleArtifacts.frameDecl(); + final var firstGlobalDecl = lifecycleArtifacts.firstGlobalDecl(); + + final var fileInitAnchorSpan = initDecl != null ? initDecl.span() : firstGlobalDecl == null ? null : firstGlobalDecl.span(); + final var fileInitAnchorName = initDecl != null ? initDecl.name() : firstGlobalDecl == null ? "" : firstGlobalDecl.name(); + if (fileInitAnchorSpan != null) { + syntheticFunctions.add(new IRSyntheticFunction( + moduleId, + fileInitCallableName(fileId), + IRSyntheticCallableKind.FILE_INIT_FRAGMENT, + new IRSyntheticOrigin(fileId, fileInitAnchorName, fileInitAnchorSpan))); + syntheticFunctions.add(new IRSyntheticFunction( + moduleId, + moduleInitCallableName(moduleId), + IRSyntheticCallableKind.MODULE_INIT, + new IRSyntheticOrigin(fileId, fileInitAnchorName, fileInitAnchorSpan))); + } + if (initDecl != null && frameDecl != null) { + syntheticFunctions.add(new IRSyntheticFunction( + moduleId, + projectInitCallableName(moduleId), + IRSyntheticCallableKind.PROJECT_INIT, + new IRSyntheticOrigin(fileId, initDecl.name(), initDecl.span()))); + } + if (frameDecl != null) { + syntheticFunctions.add(new IRSyntheticFunction( + moduleId, + frameWrapperCallableName(moduleId), + IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER, + new IRSyntheticOrigin(fileId, frameDecl.name(), frameDecl.span()))); + } + return ReadOnlyList.wrap(syntheticFunctions); + } + + private IRBackendExecutableFunction lowerFileInitExecutable( + final FileId fileId, + final ModuleId moduleId, + final PbsLifecycleArtifacts lifecycleArtifacts, + final ArrayList executableFunctions, + final ArrayList callableSignatures) { + if (lifecycleArtifacts.firstGlobalDecl() == null + && lifecycleArtifacts.initDecl() == null) { + return null; + } + final var syntheticCallableName = fileInitCallableName(fileId); + final var syntheticCallableId = new CallableId(callableSignatures.size()); + callableSignatures.add(new CallableSignatureRef(moduleId, syntheticCallableName, 0, "() -> unit")); + + final var instructions = new ArrayList(); + final var moduleInitSource = lifecycleArtifacts.initDecl(); + if (moduleInitSource != null && moduleInitSource != lifecycleArtifacts.frameDecl()) { + final var initExecutable = resolveLifecycleExecutable(executableFunctions, moduleId, moduleInitSource.name()); + if (initExecutable != null) { + instructions.add(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.CALL_FUNC, + moduleId, + initExecutable.callableName(), + initExecutable.callableId(), + null, + null, + 0, + 0, + moduleInitSource.span())); + } + } + instructions.add(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.RET, + "", + null, + null, + lifecycleArtifacts.anchorSpan())); + final var span = lifecycleArtifacts.anchorSpan(); + return new IRBackendExecutableFunction( + fileId, + moduleId, + syntheticCallableName, + syntheticCallableId, + safeToInt(span.getStart()), + safeToInt(span.getEnd()), + 0, + 0, + 0, + 1, + ReadOnlyList.wrap(instructions), + span); + } + + private IRBackendExecutableFunction resolveLifecycleExecutable( + final ArrayList executableFunctions, + final ModuleId moduleId, + final String callableName) { + for (final var executable : executableFunctions) { + if (!callableName.equals(executable.callableName())) { + continue; + } + if (!moduleIdsMatch(moduleId, executable.moduleId())) { + continue; + } + return executable; + } + return null; + } + + private boolean moduleIdsMatch( + 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 PbsLifecycleArtifacts scanLifecycleArtifacts(final PbsAst.File ast) { PbsAst.FunctionDecl initDecl = null; PbsAst.FunctionDecl frameDecl = null; PbsAst.GlobalDecl firstGlobalDecl = null; @@ -262,36 +397,56 @@ public final class PbsFrontendCompiler { firstGlobalDecl = globalDecl; } } + return new PbsLifecycleArtifacts(initDecl, frameDecl, firstGlobalDecl); + } - final var fileInitAnchorSpan = initDecl != null ? initDecl.span() : firstGlobalDecl == null ? null : firstGlobalDecl.span(); - final var fileInitAnchorName = initDecl != null ? initDecl.name() : firstGlobalDecl == null ? "" : firstGlobalDecl.name(); - if (fileInitAnchorSpan != null) { - syntheticFunctions.add(new IRSyntheticFunction( - moduleId, - "__pbs.file_init", - IRSyntheticCallableKind.FILE_INIT_FRAGMENT, - new IRSyntheticOrigin(fileId, fileInitAnchorName, fileInitAnchorSpan))); - syntheticFunctions.add(new IRSyntheticFunction( - moduleId, - "__pbs.module_init", - IRSyntheticCallableKind.MODULE_INIT, - new IRSyntheticOrigin(fileId, fileInitAnchorName, fileInitAnchorSpan))); + private static String fileInitCallableName(final FileId fileId) { + return "__pbs.file_init$f" + fileId.getId(); + } + + public static String moduleInitCallableName(final ModuleId moduleId) { + return "__pbs.module_init$m" + normalizedModuleIndex(moduleId); + } + + public static String projectInitCallableName(final ModuleId moduleId) { + return "__pbs.project_init$m" + normalizedModuleIndex(moduleId); + } + + public static String frameWrapperCallableName(final ModuleId moduleId) { + return "__pbs.frame_wrapper$m" + normalizedModuleIndex(moduleId); + } + + private static int normalizedModuleIndex(final ModuleId moduleId) { + final var normalized = moduleId == null ? ModuleId.none() : moduleId; + return normalized.isNone() ? -1 : normalized.getIndex(); + } + + private int safeToInt(final long value) { + if (value > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; } - if (initDecl != null && frameDecl != null) { - syntheticFunctions.add(new IRSyntheticFunction( - moduleId, - "__pbs.project_init", - IRSyntheticCallableKind.PROJECT_INIT, - new IRSyntheticOrigin(fileId, initDecl.name(), initDecl.span()))); + if (value < Integer.MIN_VALUE) { + return Integer.MIN_VALUE; } - if (frameDecl != null) { - syntheticFunctions.add(new IRSyntheticFunction( - moduleId, - "__pbs.frame_wrapper", - IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER, - new IRSyntheticOrigin(fileId, frameDecl.name(), frameDecl.span()))); + return (int) value; + } + + private record PbsLifecycleArtifacts( + PbsAst.FunctionDecl initDecl, + PbsAst.FunctionDecl frameDecl, + PbsAst.GlobalDecl firstGlobalDecl) { + private Span anchorSpan() { + if (initDecl != null) { + return initDecl.span(); + } + if (frameDecl != null) { + return frameDecl.span(); + } + if (firstGlobalDecl != null) { + return firstGlobalDecl.span(); + } + return Span.none(); } - return ReadOnlyList.wrap(syntheticFunctions); } private IRReservedMetadata mergeReservedMetadata( 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 4713ba05..b55ad93d 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 @@ -1,18 +1,24 @@ package p.studio.compiler.services; import lombok.extern.slf4j.Slf4j; -import p.studio.compiler.PBSDefinitions; import p.studio.compiler.messages.BuildingIssueSink; import p.studio.compiler.messages.FrontendPhaseContext; import p.studio.compiler.models.IRBackend; +import p.studio.compiler.models.IRBackendExecutableFunction; +import p.studio.compiler.models.IRSyntheticCallableKind; import p.studio.compiler.pbs.PbsFrontendCompiler; import p.studio.compiler.pbs.PbsReservedMetadataExtractor; import p.studio.compiler.pbs.stdlib.InterfaceModuleLoader; import p.studio.compiler.pbs.stdlib.ResourceStdlibEnvironmentResolver; import p.studio.compiler.pbs.stdlib.StdlibEnvironmentResolver; +import p.studio.compiler.source.Span; import p.studio.compiler.source.diagnostics.DiagnosticSink; +import p.studio.compiler.source.identifiers.CallableId; +import p.studio.compiler.source.identifiers.FileId; import p.studio.compiler.source.identifiers.ModuleId; +import p.studio.compiler.source.tables.CallableSignatureRef; import p.studio.utilities.logs.LogAggregator; +import p.studio.utilities.structures.ReadOnlyList; import java.util.*; @@ -100,29 +106,282 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { final Map> moduleDependencyGraph) { final var irBackendAggregator = IRBackend.aggregator(); final var blockedModuleIds = blockedModulesByDependency(failedModuleIds, moduleDependencyGraph); - final var entryPointCallableName = PBSDefinitions.PBS.getEntryPointCallableName(); - final var entryPointModuleCandidates = new LinkedHashSet(); for (final var compiledSource : compiledSourceFiles) { if (blockedModuleIds.contains(compiledSource.moduleId())) { continue; } irBackendAggregator.merge(compiledSource.irBackendFile()); - for (final var executable : compiledSource.irBackendFile().executableFunctions()) { - if (!entryPointCallableName.equals(executable.callableName())) { - continue; + } + return synthesizePublishedLifecycle(irBackendAggregator.emit()); + } + + private IRBackend synthesizePublishedLifecycle(final IRBackend baseBackend) { + final var callableSignatures = new ArrayList<>(baseBackend.getCallableSignatures().asList()); + final var executableFunctions = new ArrayList<>(baseBackend.getExecutableFunctions().asList()); + final var fileInitFunctionsByModule = new LinkedHashMap>(); + final var projectInitAnchorsByModule = new LinkedHashMap(); + final var wrapperNamesByModule = new LinkedHashMap(); + final var frameAnchorsByModule = new LinkedHashMap(); + + for (final var executable : executableFunctions) { + if (!executable.callableName().startsWith("__pbs.file_init$")) { + continue; + } + fileInitFunctionsByModule + .computeIfAbsent(normalizeModuleId(executable.moduleId()), ignored -> new ArrayList<>()) + .add(executable); + } + for (final var syntheticFunction : baseBackend.getSyntheticFunctions()) { + final var moduleId = normalizeModuleId(syntheticFunction.moduleId()); + switch (syntheticFunction.kind()) { + case PROJECT_INIT -> projectInitAnchorsByModule.put(moduleId, syntheticFunction.origin().anchorCallableName()); + case PUBLISHED_FRAME_WRAPPER -> { + wrapperNamesByModule.put(moduleId, syntheticFunction.callableName()); + frameAnchorsByModule.put(moduleId, syntheticFunction.origin().anchorCallableName()); } - if (executable.moduleId() != null && !executable.moduleId().isNone()) { - entryPointModuleCandidates.add(executable.moduleId()); + default -> { } } } - irBackendAggregator.entryPointCallableName(entryPointCallableName); - if (entryPointModuleCandidates.size() == 1) { - irBackendAggregator.entryPointModuleId(entryPointModuleCandidates.iterator().next()); + final ModuleId entryPointModuleId; + final String entryPointCallableName; + if (wrapperNamesByModule.size() == 1) { + entryPointModuleId = wrapperNamesByModule.keySet().iterator().next(); + entryPointCallableName = wrapperNamesByModule.get(entryPointModuleId); + } else { + entryPointModuleId = baseBackend.getEntryPointModuleId(); + entryPointCallableName = baseBackend.getEntryPointCallableName(); } - return irBackendAggregator.emit(); + + final var sortedModuleIds = new ArrayList(); + sortedModuleIds.addAll(fileInitFunctionsByModule.keySet()); + for (final var moduleId : wrapperNamesByModule.keySet()) { + if (!sortedModuleIds.contains(moduleId)) { + sortedModuleIds.add(moduleId); + } + } + sortedModuleIds.sort(Comparator.comparing(moduleId -> moduleSortKey(baseBackend, moduleId))); + + final var moduleInitCallableIds = new LinkedHashMap(); + for (final var moduleId : sortedModuleIds) { + final var moduleInitName = PbsFrontendCompiler.moduleInitCallableName(moduleId); + final var fileInitFunctions = new ArrayList<>(fileInitFunctionsByModule.getOrDefault(moduleId, List.of())); + if (fileInitFunctions.isEmpty()) { + continue; + } + fileInitFunctions.sort(Comparator.comparingInt(fn -> fn.fileId().getId())); + + final var callableId = new CallableId(callableSignatures.size()); + callableSignatures.add(new CallableSignatureRef(moduleId, moduleInitName, 0, "() -> unit")); + moduleInitCallableIds.put(moduleId, callableId); + + final var instructions = new ArrayList(); + var anchorSpan = fileInitFunctions.getFirst().span(); + var anchorFileId = fileInitFunctions.getFirst().fileId(); + for (final var fileInitFunction : fileInitFunctions) { + instructions.add(callInstruction(fileInitFunction, fileInitFunction.span())); + anchorSpan = fileInitFunction.span(); + anchorFileId = fileInitFunction.fileId(); + } + instructions.add(retInstruction(anchorSpan)); + executableFunctions.add(new IRBackendExecutableFunction( + anchorFileId, + moduleId, + moduleInitName, + callableId, + safeToInt(anchorSpan.getStart()), + safeToInt(anchorSpan.getEnd()), + 0, + 0, + 0, + 1, + ReadOnlyList.wrap(instructions), + anchorSpan)); + } + + CallableId projectInitCallableId = null; + if (!entryPointModuleId.isNone()) { + final var projectInitAnchor = projectInitAnchorsByModule.get(entryPointModuleId); + final var projectInitTarget = resolveExecutable(executableFunctions, entryPointModuleId, projectInitAnchor); + if (projectInitTarget != null) { + final var projectInitName = PbsFrontendCompiler.projectInitCallableName(entryPointModuleId); + projectInitCallableId = new CallableId(callableSignatures.size()); + callableSignatures.add(new CallableSignatureRef(entryPointModuleId, projectInitName, 0, "() -> unit")); + executableFunctions.add(new IRBackendExecutableFunction( + projectInitTarget.fileId(), + entryPointModuleId, + projectInitName, + projectInitCallableId, + projectInitTarget.sourceStart(), + projectInitTarget.sourceEnd(), + 0, + 0, + 0, + 1, + ReadOnlyList.from( + callInstruction(projectInitTarget, projectInitTarget.span()), + retInstruction(projectInitTarget.span())), + projectInitTarget.span())); + } + } + + final var frameExecutable = resolveExecutable( + executableFunctions, + entryPointModuleId, + frameAnchorsByModule.get(entryPointModuleId)); + if (frameExecutable != null + && entryPointCallableName != null + && !entryPointCallableName.isBlank() + && !entryPointModuleId.isNone()) { + final var wrapperCallableId = new CallableId(callableSignatures.size()); + callableSignatures.add(new CallableSignatureRef(entryPointModuleId, entryPointCallableName, 0, "() -> unit")); + final var instructions = new ArrayList(); + for (final var moduleId : sortedModuleIds) { + final var moduleInitCallableId = moduleInitCallableIds.get(moduleId); + if (moduleInitCallableId == null) { + continue; + } + instructions.add(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.CALL_FUNC, + moduleId, + PbsFrontendCompiler.moduleInitCallableName(moduleId), + moduleInitCallableId, + null, + null, + 0, + 0, + frameExecutable.span())); + } + if (projectInitCallableId != null) { + instructions.add(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.CALL_FUNC, + entryPointModuleId, + PbsFrontendCompiler.projectInitCallableName(entryPointModuleId), + projectInitCallableId, + null, + null, + 0, + 0, + frameExecutable.span())); + } + instructions.add(callInstruction(frameExecutable, frameExecutable.span())); + instructions.add(retInstruction(frameExecutable.span())); + executableFunctions.add(new IRBackendExecutableFunction( + frameExecutable.fileId(), + entryPointModuleId, + entryPointCallableName, + wrapperCallableId, + frameExecutable.sourceStart(), + frameExecutable.sourceEnd(), + 0, + 0, + 0, + 1, + ReadOnlyList.wrap(instructions), + frameExecutable.span())); + } + + return IRBackend.builder() + .entryPointCallableName(entryPointCallableName) + .entryPointModuleId(entryPointModuleId) + .functions(baseBackend.getFunctions()) + .syntheticFunctions(baseBackend.getSyntheticFunctions()) + .globals(baseBackend.getGlobals()) + .executableFunctions(ReadOnlyList.wrap(executableFunctions)) + .modulePool(baseBackend.getModulePool()) + .callableSignatures(ReadOnlyList.wrap(callableSignatures)) + .intrinsicPool(baseBackend.getIntrinsicPool()) + .reservedMetadata(baseBackend.getReservedMetadata()) + .build(); + } + + private IRBackendExecutableFunction resolveExecutable( + final ArrayList executableFunctions, + final ModuleId moduleId, + final String callableName) { + if (callableName == null || callableName.isBlank()) { + return null; + } + for (final var executable : executableFunctions) { + if (!callableName.equals(executable.callableName())) { + continue; + } + if (!moduleIdsMatch(moduleId, executable.moduleId())) { + continue; + } + return executable; + } + return null; + } + + private IRBackendExecutableFunction.Instruction callInstruction( + final IRBackendExecutableFunction callee, + final Span span) { + return new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.CALL_FUNC, + callee.moduleId(), + callee.callableName(), + callee.callableId(), + null, + null, + callee.paramSlots(), + callee.returnSlots(), + span); + } + + private IRBackendExecutableFunction.Instruction retInstruction(final Span span) { + return new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.RET, + "", + null, + null, + span); + } + + private ModuleId normalizeModuleId(final ModuleId moduleId) { + return moduleId == null ? ModuleId.none() : moduleId; + } + + private boolean moduleIdsMatch( + final ModuleId left, + final ModuleId right) { + final var normalizedLeft = normalizeModuleId(left); + final var normalizedRight = normalizeModuleId(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) { + if (moduleId != null && !moduleId.isNone()) { + final var index = moduleId.getIndex(); + if (index >= 0 && index < backend.getModulePool().size()) { + final var moduleReference = backend.getModulePool().get(index); + final var joiner = new StringJoiner("/", moduleReference.project() + ":", ""); + for (final var segment : moduleReference.pathSegments()) { + joiner.add(segment); + } + return joiner.toString(); + } + } + return ""; + } + + private int safeToInt(final long value) { + if (value > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + if (value < Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } + return (int) value; } private Set blockedModulesByDependency( 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 49804c0b..adac6436 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 @@ -254,8 +254,69 @@ class PBSFrontendPhaseServiceTest { BuildingIssueSink.empty()); assertTrue(diagnostics.isEmpty()); - assertEquals("frame", irBackend.getEntryPointCallableName()); assertEquals(2, irBackend.getFunctions().size()); + assertEquals(2, irBackend.getExecutableFunctions().size()); + } + + @Test + void shouldPublishSyntheticFrameWrapperAsEntrypointWhenLifecycleMarkersExist() throws IOException { + final var projectRoot = tempDir.resolve("project-lifecycle-wrapper"); + final var sourceRoot = projectRoot.resolve("src"); + final var modulePath = sourceRoot.resolve("game"); + Files.createDirectories(modulePath); + + final var sourceFile = modulePath.resolve("source.pbs"); + final var modBarrel = modulePath.resolve("mod.barrel"); + Files.writeString(sourceFile, """ + declare global SCORE: int = 0; + + [Init] + fn boot() -> void { return; } + + [Frame] + fn frame() -> void { return; } + """); + Files.writeString(modBarrel, """ + 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, sourceFile, 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")); + final var wrapper = irBackend.getExecutableFunctions().stream() + .filter(function -> irBackend.getEntryPointCallableName().equals(function.callableName())) + .findFirst() + .orElseThrow(); + assertEquals(4, wrapper.instructions().size()); + assertTrue(wrapper.instructions().get(0).calleeCallableName().startsWith("__pbs.module_init$m")); + assertTrue(wrapper.instructions().get(1).calleeCallableName().startsWith("__pbs.project_init$m")); + assertEquals("frame", wrapper.instructions().get(2).calleeCallableName()); + assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.RET, wrapper.instructions().get(3).kind()); } @Test 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 1f40311a..1b5a5b96 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 @@ -57,6 +57,44 @@ class LowerToIRVMServiceTest { assertEquals(0, lowered.module().functions().get(1).instructions().get(0).immediate()); } + @Test + void lowerMustPublishSyntheticWrapperAtFunctionZeroAndKeepFinalRetInWrapperPath() { + final var backend = IRBackend.builder() + .entryPointCallableName("__pbs.frame_wrapper$m0") + .entryPointModuleId(new ModuleId(0)) + .executableFunctions(ReadOnlyList.from( + fn("__pbs.file_init$f1", "app", 30, ReadOnlyList.from(ret())), + fn("__pbs.module_init$m0", "app", 31, ReadOnlyList.from( + callFunc("app", "__pbs.file_init$f1", 30), + ret())), + fn("__pbs.project_init$m0", "app", 32, ReadOnlyList.from( + callFunc("app", "boot", 10), + ret())), + fn("boot", "app", 10, ReadOnlyList.from(ret())), + fn("frame", "app", 20, ReadOnlyList.from(ret())), + fn("__pbs.frame_wrapper$m0", "app", 40, ReadOnlyList.from( + callFunc("app", "__pbs.module_init$m0", 31), + callFunc("app", "__pbs.project_init$m0", 32), + callFunc("app", "frame", 20), + ret())))) + .build(); + + final var lowered = new LowerToIRVMService().lower(backend); + + assertEquals("__pbs.frame_wrapper$m0", lowered.module().functions().get(0).name()); + assertEquals(IRVMOp.CALL, lowered.module().functions().get(0).instructions().get(0).op()); + assertEquals(IRVMOp.CALL, lowered.module().functions().get(0).instructions().get(1).op()); + assertEquals(IRVMOp.CALL, lowered.module().functions().get(0).instructions().get(2).op()); + assertEquals(IRVMOp.RET, lowered.module().functions().get(0).instructions().get(3).op()); + assertEquals(IRVMOp.RET, lowered.module().functions().stream() + .filter(function -> "frame".equals(function.name())) + .findFirst() + .orElseThrow() + .instructions() + .getLast() + .op()); + } + @Test void lowerMustMapHostAndIntrinsicCallsites() { final var backend = IRBackend.builder()