implements PR-19.8 published wrapper entrypoint lowering

This commit is contained in:
bQUARKz 2026-03-26 19:38:58 +00:00
parent c61b9db786
commit 58c5ab26d7
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
5 changed files with 554 additions and 42 deletions

View File

@ -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();
}

View File

@ -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<IRSyntheticFunction>();
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<IRBackendExecutableFunction> executableFunctions,
final ArrayList<CallableSignatureRef> 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<IRBackendExecutableFunction.Instruction>();
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<IRBackendExecutableFunction> 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;
}
}
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)));
return new PbsLifecycleArtifacts(initDecl, frameDecl, firstGlobalDecl);
}
if (initDecl != null && frameDecl != null) {
syntheticFunctions.add(new IRSyntheticFunction(
moduleId,
"__pbs.project_init",
IRSyntheticCallableKind.PROJECT_INIT,
new IRSyntheticOrigin(fileId, initDecl.name(), initDecl.span())));
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 (value < Integer.MIN_VALUE) {
return Integer.MIN_VALUE;
}
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) {
syntheticFunctions.add(new IRSyntheticFunction(
moduleId,
"__pbs.frame_wrapper",
IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
new IRSyntheticOrigin(fileId, frameDecl.name(), frameDecl.span())));
return frameDecl.span();
}
if (firstGlobalDecl != null) {
return firstGlobalDecl.span();
}
return Span.none();
}
return ReadOnlyList.wrap(syntheticFunctions);
}
private IRReservedMetadata mergeReservedMetadata(

View File

@ -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<ModuleId, Set<ModuleId>> moduleDependencyGraph) {
final var irBackendAggregator = IRBackend.aggregator();
final var blockedModuleIds = blockedModulesByDependency(failedModuleIds, moduleDependencyGraph);
final var entryPointCallableName = PBSDefinitions.PBS.getEntryPointCallableName();
final var entryPointModuleCandidates = new LinkedHashSet<ModuleId>();
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())) {
}
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<ModuleId, List<IRBackendExecutableFunction>>();
final var projectInitAnchorsByModule = new LinkedHashMap<ModuleId, String>();
final var wrapperNamesByModule = new LinkedHashMap<ModuleId, String>();
final var frameAnchorsByModule = new LinkedHashMap<ModuleId, String>();
for (final var executable : executableFunctions) {
if (!executable.callableName().startsWith("__pbs.file_init$")) {
continue;
}
if (executable.moduleId() != null && !executable.moduleId().isNone()) {
entryPointModuleCandidates.add(executable.moduleId());
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());
}
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<ModuleId>();
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<ModuleId, CallableId>();
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<IRBackendExecutableFunction.Instruction>();
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<IRBackendExecutableFunction.Instruction>();
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<IRBackendExecutableFunction> 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<ModuleId> blockedModulesByDependency(

View File

@ -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

View File

@ -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()