implements PR-19.7 explicit backend globals and synthetic callable model

This commit is contained in:
bQUARKz 2026-03-26 19:31:21 +00:00
parent 7fc38010a2
commit c61b9db786
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
8 changed files with 238 additions and 0 deletions

View File

@ -3,7 +3,11 @@ package p.studio.compiler.pbs;
import p.studio.compiler.messages.HostAdmissionContext; import p.studio.compiler.messages.HostAdmissionContext;
import p.studio.compiler.models.IRBackendFile; import p.studio.compiler.models.IRBackendFile;
import p.studio.compiler.models.IRFunction; import p.studio.compiler.models.IRFunction;
import p.studio.compiler.models.IRGlobal;
import p.studio.compiler.models.IRReservedMetadata; import p.studio.compiler.models.IRReservedMetadata;
import p.studio.compiler.models.IRSyntheticCallableKind;
import p.studio.compiler.models.IRSyntheticFunction;
import p.studio.compiler.models.IRSyntheticOrigin;
import p.studio.compiler.models.SourceKind; import p.studio.compiler.models.SourceKind;
import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.pbs.lexer.PbsLexer; import p.studio.compiler.pbs.lexer.PbsLexer;
@ -149,6 +153,12 @@ public final class PbsFrontendCompiler {
final ReadOnlyList<IRFunction> functions = sourceKind == SourceKind.SDK_INTERFACE final ReadOnlyList<IRFunction> functions = sourceKind == SourceKind.SDK_INTERFACE
? ReadOnlyList.empty() ? ReadOnlyList.empty()
: lowerFunctions(fileId, ast); : lowerFunctions(fileId, ast);
final ReadOnlyList<IRSyntheticFunction> syntheticFunctions = sourceKind == SourceKind.SDK_INTERFACE
? ReadOnlyList.empty()
: lowerSyntheticFunctions(fileId, effectiveModuleId, ast);
final ReadOnlyList<IRGlobal> globals = sourceKind == SourceKind.SDK_INTERFACE
? ReadOnlyList.empty()
: lowerGlobals(fileId, effectiveModuleId, ast);
final var loweringErrorBaseline = diagnostics.errorCount(); final var loweringErrorBaseline = diagnostics.errorCount();
final var executableLowering = executableLoweringService.lower( final var executableLowering = executableLoweringService.lower(
fileId, fileId,
@ -165,6 +175,8 @@ public final class PbsFrontendCompiler {
return new IRBackendFile( return new IRBackendFile(
fileId, fileId,
functions, functions,
syntheticFunctions,
globals,
executableLowering.executableFunctions(), executableLowering.executableFunctions(),
reservedMetadata, reservedMetadata,
effectiveModulePool, effectiveModulePool,
@ -210,6 +222,78 @@ public final class PbsFrontendCompiler {
return ReadOnlyList.wrap(functions); return ReadOnlyList.wrap(functions);
} }
private ReadOnlyList<IRGlobal> lowerGlobals(
final FileId fileId,
final ModuleId moduleId,
final PbsAst.File ast) {
final var globals = new ArrayList<IRGlobal>();
for (final var topDecl : ast.topDecls()) {
if (!(topDecl instanceof PbsAst.GlobalDecl globalDecl)) {
continue;
}
globals.add(new IRGlobal(
fileId,
moduleId,
globalDecl.name(),
PbsReservedMetadataExtractor.typeSurfaceKey(globalDecl.explicitType()),
globalDecl.span()));
}
return ReadOnlyList.wrap(globals);
}
private ReadOnlyList<IRSyntheticFunction> lowerSyntheticFunctions(
final FileId fileId,
final ModuleId moduleId,
final PbsAst.File ast) {
final var syntheticFunctions = new ArrayList<IRSyntheticFunction>();
PbsAst.FunctionDecl initDecl = null;
PbsAst.FunctionDecl frameDecl = null;
PbsAst.GlobalDecl firstGlobalDecl = null;
for (final var topDecl : ast.topDecls()) {
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
if (functionDecl.lifecycleMarker() == PbsAst.LifecycleMarker.INIT && initDecl == null) {
initDecl = functionDecl;
}
if (functionDecl.lifecycleMarker() == PbsAst.LifecycleMarker.FRAME && frameDecl == null) {
frameDecl = functionDecl;
}
}
if (topDecl instanceof PbsAst.GlobalDecl globalDecl && firstGlobalDecl == null) {
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)));
}
if (initDecl != null && frameDecl != null) {
syntheticFunctions.add(new IRSyntheticFunction(
moduleId,
"__pbs.project_init",
IRSyntheticCallableKind.PROJECT_INIT,
new IRSyntheticOrigin(fileId, initDecl.name(), 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 ReadOnlyList.wrap(syntheticFunctions);
}
private IRReservedMetadata mergeReservedMetadata( private IRReservedMetadata mergeReservedMetadata(
final IRReservedMetadata primary, final IRReservedMetadata primary,
final IRReservedMetadata imported) { final IRReservedMetadata imported) {

View File

@ -2,6 +2,7 @@ package p.studio.compiler.pbs;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import p.studio.compiler.messages.HostAdmissionContext; import p.studio.compiler.messages.HostAdmissionContext;
import p.studio.compiler.models.IRSyntheticCallableKind;
import p.studio.compiler.models.SourceKind; import p.studio.compiler.models.SourceKind;
import p.studio.compiler.pbs.lexer.LexErrors; import p.studio.compiler.pbs.lexer.LexErrors;
import p.studio.compiler.pbs.semantics.PbsSemanticsErrors; import p.studio.compiler.pbs.semantics.PbsSemanticsErrors;
@ -40,6 +41,33 @@ class PbsFrontendCompilerTest {
assertEquals(1, functions.get(1).parameterCount()); assertEquals(1, functions.get(1).parameterCount());
} }
@Test
void shouldExposeGlobalsAndSyntheticLifecycleArtifactsInBackendFile() {
final var source = """
declare global SCORE: int = 0;
[Init]
fn boot() -> void { return; }
[Frame]
fn frame() -> void { return; }
""";
final var diagnostics = DiagnosticSink.empty();
final var compiler = new PbsFrontendCompiler();
final var fileBackend = compiler.compileFile(new FileId(1), source, diagnostics);
assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
assertEquals(1, fileBackend.globals().size());
assertEquals("SCORE", fileBackend.globals().getFirst().name());
assertEquals(4, fileBackend.syntheticFunctions().size());
assertTrue(fileBackend.syntheticFunctions().stream().anyMatch(fn -> fn.kind() == IRSyntheticCallableKind.FILE_INIT_FRAGMENT));
assertTrue(fileBackend.syntheticFunctions().stream().anyMatch(fn -> fn.kind() == IRSyntheticCallableKind.MODULE_INIT));
assertTrue(fileBackend.syntheticFunctions().stream().anyMatch(fn -> fn.kind() == IRSyntheticCallableKind.PROJECT_INIT));
assertTrue(fileBackend.syntheticFunctions().stream().anyMatch(fn -> fn.kind() == IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER));
assertTrue(fileBackend.syntheticFunctions().stream().allMatch(fn -> !fn.origin().anchorCallableName().isBlank()));
}
@Test @Test
void shouldReportDuplicateFunctionNames() { void shouldReportDuplicateFunctionNames() {
final var source = """ final var source = """

View File

@ -21,6 +21,10 @@ public class IRBackend {
@Builder.Default @Builder.Default
private final ReadOnlyList<IRFunction> functions = ReadOnlyList.empty(); private final ReadOnlyList<IRFunction> functions = ReadOnlyList.empty();
@Builder.Default @Builder.Default
private final ReadOnlyList<IRSyntheticFunction> syntheticFunctions = ReadOnlyList.empty();
@Builder.Default
private final ReadOnlyList<IRGlobal> globals = ReadOnlyList.empty();
@Builder.Default
private final ReadOnlyList<IRBackendExecutableFunction> executableFunctions = ReadOnlyList.empty(); private final ReadOnlyList<IRBackendExecutableFunction> executableFunctions = ReadOnlyList.empty();
@Builder.Default @Builder.Default
private final ReadOnlyList<ModuleReference> modulePool = ReadOnlyList.empty(); private final ReadOnlyList<ModuleReference> modulePool = ReadOnlyList.empty();
@ -39,6 +43,8 @@ public class IRBackend {
private String entryPointCallableName; private String entryPointCallableName;
private ModuleId entryPointModuleId = ModuleId.none(); private ModuleId entryPointModuleId = ModuleId.none();
private final ArrayList<IRFunction> functions = new ArrayList<>(); private final ArrayList<IRFunction> functions = new ArrayList<>();
private final ArrayList<IRSyntheticFunction> syntheticFunctions = new ArrayList<>();
private final ArrayList<IRGlobal> globals = new ArrayList<>();
private final ArrayList<IRBackendExecutableFunction> executableFunctions = new ArrayList<>(); private final ArrayList<IRBackendExecutableFunction> executableFunctions = new ArrayList<>();
private final ModuleTable moduleTable = new ModuleTable(); private final ModuleTable moduleTable = new ModuleTable();
private final CallableTable callableTable = new CallableTable(); private final CallableTable callableTable = new CallableTable();
@ -54,6 +60,21 @@ public class IRBackend {
} }
functions.addAll(backendFile.functions().asList()); functions.addAll(backendFile.functions().asList());
final var moduleRemap = reindexModules(backendFile.modulePool()); final var moduleRemap = reindexModules(backendFile.modulePool());
for (final var syntheticFunction : backendFile.syntheticFunctions()) {
syntheticFunctions.add(new IRSyntheticFunction(
remapModuleId(syntheticFunction.moduleId(), moduleRemap, "synthetic function"),
syntheticFunction.callableName(),
syntheticFunction.kind(),
syntheticFunction.origin()));
}
for (final var global : backendFile.globals()) {
globals.add(new IRGlobal(
global.fileId(),
remapModuleId(global.moduleId(), moduleRemap, "global"),
global.name(),
global.declaredTypeSurface(),
global.span()));
}
final var callableRemap = reindexCallables(backendFile.callableSignatures(), moduleRemap); final var callableRemap = reindexCallables(backendFile.callableSignatures(), moduleRemap);
final var intrinsicRemap = reindexIntrinsics(backendFile.intrinsicPool()); final var intrinsicRemap = reindexIntrinsics(backendFile.intrinsicPool());
for (final var function : backendFile.executableFunctions()) { for (final var function : backendFile.executableFunctions()) {
@ -248,6 +269,8 @@ public class IRBackend {
.entryPointCallableName(resolveEntryPointCallableName()) .entryPointCallableName(resolveEntryPointCallableName())
.entryPointModuleId(resolveEntryPointModuleId()) .entryPointModuleId(resolveEntryPointModuleId())
.functions(ReadOnlyList.wrap(functions)) .functions(ReadOnlyList.wrap(functions))
.syntheticFunctions(ReadOnlyList.wrap(syntheticFunctions))
.globals(ReadOnlyList.wrap(globals))
.executableFunctions(ReadOnlyList.wrap(executableFunctions)) .executableFunctions(ReadOnlyList.wrap(executableFunctions))
.modulePool(emitModulePool()) .modulePool(emitModulePool())
.callableSignatures(emitCallableSignatures()) .callableSignatures(emitCallableSignatures())
@ -278,6 +301,8 @@ public class IRBackend {
.append('@') .append('@')
.append(entryPointModuleId == null || entryPointModuleId.isNone() ? "-" : entryPointModuleId.getIndex()) .append(entryPointModuleId == null || entryPointModuleId.isNone() ? "-" : entryPointModuleId.getIndex())
.append(", functions=").append(functions.size()) .append(", functions=").append(functions.size())
.append(", syntheticFunctions=").append(syntheticFunctions.size())
.append(", globals=").append(globals.size())
.append(", executableFunctions=").append(executableFunctions.size()) .append(", executableFunctions=").append(executableFunctions.size())
.append(", modulePool=").append(modulePool.size()) .append(", modulePool=").append(modulePool.size())
.append(", callableSignatures=").append(callableSignatures.size()) .append(", callableSignatures=").append(callableSignatures.size())

View File

@ -11,6 +11,8 @@ import java.util.Objects;
public record IRBackendFile( public record IRBackendFile(
FileId fileId, FileId fileId,
ReadOnlyList<IRFunction> functions, ReadOnlyList<IRFunction> functions,
ReadOnlyList<IRSyntheticFunction> syntheticFunctions,
ReadOnlyList<IRGlobal> globals,
ReadOnlyList<IRBackendExecutableFunction> executableFunctions, ReadOnlyList<IRBackendExecutableFunction> executableFunctions,
IRReservedMetadata reservedMetadata, IRReservedMetadata reservedMetadata,
ReadOnlyList<ModuleReference> modulePool, ReadOnlyList<ModuleReference> modulePool,
@ -19,6 +21,8 @@ public record IRBackendFile(
public IRBackendFile { public IRBackendFile {
fileId = Objects.requireNonNull(fileId, "fileId"); fileId = Objects.requireNonNull(fileId, "fileId");
functions = functions == null ? ReadOnlyList.empty() : functions; functions = functions == null ? ReadOnlyList.empty() : functions;
syntheticFunctions = syntheticFunctions == null ? ReadOnlyList.empty() : syntheticFunctions;
globals = globals == null ? ReadOnlyList.empty() : globals;
executableFunctions = executableFunctions == null ? ReadOnlyList.empty() : executableFunctions; executableFunctions = executableFunctions == null ? ReadOnlyList.empty() : executableFunctions;
reservedMetadata = reservedMetadata == null ? IRReservedMetadata.empty() : reservedMetadata; reservedMetadata = reservedMetadata == null ? IRReservedMetadata.empty() : reservedMetadata;
modulePool = modulePool == null ? ReadOnlyList.empty() : modulePool; modulePool = modulePool == null ? ReadOnlyList.empty() : modulePool;
@ -33,6 +37,8 @@ public record IRBackendFile(
fileId, fileId,
functions, functions,
ReadOnlyList.empty(), ReadOnlyList.empty(),
ReadOnlyList.empty(),
ReadOnlyList.empty(),
IRReservedMetadata.empty(), IRReservedMetadata.empty(),
ReadOnlyList.empty(), ReadOnlyList.empty(),
ReadOnlyList.empty(), ReadOnlyList.empty(),
@ -44,6 +50,8 @@ public record IRBackendFile(
fileId, fileId,
ReadOnlyList.empty(), ReadOnlyList.empty(),
ReadOnlyList.empty(), ReadOnlyList.empty(),
ReadOnlyList.empty(),
ReadOnlyList.empty(),
IRReservedMetadata.empty(), IRReservedMetadata.empty(),
ReadOnlyList.empty(), ReadOnlyList.empty(),
ReadOnlyList.empty(), ReadOnlyList.empty(),
@ -58,6 +66,8 @@ public record IRBackendFile(
fileId, fileId,
functions, functions,
ReadOnlyList.empty(), ReadOnlyList.empty(),
ReadOnlyList.empty(),
ReadOnlyList.empty(),
reservedMetadata, reservedMetadata,
ReadOnlyList.empty(), ReadOnlyList.empty(),
ReadOnlyList.empty(), ReadOnlyList.empty(),
@ -74,10 +84,32 @@ public record IRBackendFile(
this( this(
fileId, fileId,
functions, functions,
ReadOnlyList.empty(),
ReadOnlyList.empty(),
executableFunctions, executableFunctions,
reservedMetadata, reservedMetadata,
ReadOnlyList.empty(), ReadOnlyList.empty(),
callableSignatures, callableSignatures,
intrinsicPool); intrinsicPool);
} }
public IRBackendFile(
final FileId fileId,
final ReadOnlyList<IRFunction> functions,
final ReadOnlyList<IRBackendExecutableFunction> executableFunctions,
final IRReservedMetadata reservedMetadata,
final ReadOnlyList<ModuleReference> modulePool,
final ReadOnlyList<CallableSignatureRef> callableSignatures,
final ReadOnlyList<IntrinsicReference> intrinsicPool) {
this(
fileId,
functions,
ReadOnlyList.empty(),
ReadOnlyList.empty(),
executableFunctions,
reservedMetadata,
modulePool,
callableSignatures,
intrinsicPool);
}
} }

View File

@ -0,0 +1,23 @@
package p.studio.compiler.models;
import p.studio.compiler.source.Span;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.compiler.source.identifiers.ModuleId;
import java.util.Objects;
public record IRGlobal(
FileId fileId,
ModuleId moduleId,
String name,
String declaredTypeSurface,
Span span) {
public IRGlobal {
fileId = Objects.requireNonNull(fileId, "fileId");
moduleId = moduleId == null ? ModuleId.none() : moduleId;
name = Objects.requireNonNull(name, "name");
declaredTypeSurface = declaredTypeSurface == null ? "" : declaredTypeSurface;
span = span == null ? Span.none() : span;
}
}

View File

@ -0,0 +1,9 @@
package p.studio.compiler.models;
public enum IRSyntheticCallableKind {
FILE_INIT_FRAGMENT,
MODULE_INIT,
PROJECT_INIT,
PUBLISHED_FRAME_WRAPPER
}

View File

@ -0,0 +1,19 @@
package p.studio.compiler.models;
import p.studio.compiler.source.identifiers.ModuleId;
import java.util.Objects;
public record IRSyntheticFunction(
ModuleId moduleId,
String callableName,
IRSyntheticCallableKind kind,
IRSyntheticOrigin origin) {
public IRSyntheticFunction {
moduleId = moduleId == null ? ModuleId.none() : moduleId;
callableName = Objects.requireNonNull(callableName, "callableName");
kind = Objects.requireNonNull(kind, "kind");
origin = Objects.requireNonNull(origin, "origin");
}
}

View File

@ -0,0 +1,18 @@
package p.studio.compiler.models;
import p.studio.compiler.source.Span;
import p.studio.compiler.source.identifiers.FileId;
import java.util.Objects;
public record IRSyntheticOrigin(
FileId fileId,
String anchorCallableName,
Span span) {
public IRSyntheticOrigin {
fileId = Objects.requireNonNull(fileId, "fileId");
anchorCallableName = anchorCallableName == null ? "" : anchorCallableName;
span = span == null ? Span.none() : span;
}
}