implements PR-08.3

This commit is contained in:
bQUARKz 2026-03-09 14:25:17 +00:00
parent 0490f64870
commit 2a68d27eca
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
5 changed files with 118 additions and 5 deletions

View File

@ -38,6 +38,7 @@ import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@ -191,13 +192,26 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
}
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())) {
continue;
}
if (executable.moduleId() != null && !executable.moduleId().isNone()) {
entryPointModuleCandidates.add(executable.moduleId());
}
}
}
irBackendAggregator.entryPointCallableName(entryPointCallableName);
if (entryPointModuleCandidates.size() == 1) {
irBackendAggregator.entryPointModuleId(entryPointModuleCandidates.iterator().next());
}
irBackendAggregator.entryPointCallableName(PBSDefinitions.PBS.getEntryPointCallableName());
final var irBackend = irBackendAggregator.emit();
logs.using(log).debug("PBS frontend lowered to IR BE:\n%s".formatted(irBackend));

View File

@ -296,6 +296,7 @@ public class LowerToIRVMService {
IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_DECLARATION_MISSING,
"frontend IRBackend entrypoint declaration is missing");
}
final var entryPointModuleId = backend.getEntryPointModuleId();
final var sorted = new ArrayList<>(backend.getExecutableFunctions().asList());
sorted.sort((left, right) -> {
final var leftModuleKey = moduleSortKey(backend, left.moduleId(), left.moduleKey());
@ -314,15 +315,39 @@ public class LowerToIRVMService {
}
return Integer.compare(left.sourceStart(), right.sourceStart());
});
final var entrypoints = sorted.stream()
.filter(candidate -> entryPointCallableName.equals(candidate.callableName()))
.toList();
final java.util.List<IRBackendExecutableFunction> entrypoints;
if (entryPointModuleId != null && !entryPointModuleId.isNone()) {
final var expectedModuleIndex = entryPointModuleId.getIndex();
entrypoints = sorted.stream()
.filter(candidate -> entryPointCallableName.equals(candidate.callableName()))
.filter(candidate -> !candidate.moduleId().isNone() && candidate.moduleId().getIndex() == expectedModuleIndex)
.toList();
} else {
entrypoints = sorted.stream()
.filter(candidate -> entryPointCallableName.equals(candidate.callableName()))
.toList();
}
if (entrypoints.isEmpty()) {
if (entryPointModuleId != null && !entryPointModuleId.isNone()) {
throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_MISSING,
"missing qualified entrypoint callable '%s' for moduleId=%d".formatted(
entryPointCallableName,
entryPointModuleId.getIndex()));
}
throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_MISSING,
"missing entrypoint callable '%s'".formatted(entryPointCallableName));
}
if (entrypoints.size() > 1) {
if (entryPointModuleId != null && !entryPointModuleId.isNone()) {
throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_AMBIGUOUS,
"ambiguous qualified entrypoint: found %d callables named '%s' in moduleId=%d".formatted(
entrypoints.size(),
entryPointCallableName,
entryPointModuleId.getIndex()));
}
throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_AMBIGUOUS,
"ambiguous entrypoint: found %d callables named '%s'".formatted(

View File

@ -222,6 +222,41 @@ class LowerToIRVMServiceTest {
assertEquals("helper_in_zeta", lowered.module().functions().get(2).name());
}
@Test
void lowerMustUseQualifiedEntrypointIdentityWhenProvided() {
final var backend = IRBackend.builder()
.entryPointCallableName("main")
.entryPointModuleId(new ModuleId(1))
.modulePool(ReadOnlyList.from(
new ModuleReference("app", ReadOnlyList.from("alpha")),
new ModuleReference("app", ReadOnlyList.from("beta"))))
.executableFunctions(ReadOnlyList.from(
fnWithModuleAndLocals("main", "legacy/alpha", 0, 30, 1, ReadOnlyList.from(ret())),
fnWithModuleAndLocals("main", "legacy/beta", 1, 31, 9, ReadOnlyList.from(ret())),
fnWithModule("helper", "legacy/alpha", 0, 32, ReadOnlyList.from(ret()))))
.build();
final var lowered = new LowerToIRVMService().lower(backend);
assertEquals("main", lowered.module().functions().get(0).name());
assertEquals(9, lowered.module().functions().get(0).localSlots());
}
@Test
void lowerMustRejectMissingQualifiedEntrypointIdentity() {
final var backend = IRBackend.builder()
.entryPointCallableName("main")
.entryPointModuleId(new ModuleId(5))
.modulePool(ReadOnlyList.from(new ModuleReference("app", ReadOnlyList.from("alpha"))))
.executableFunctions(ReadOnlyList.from(
fnWithModule("main", "legacy/alpha", 0, 40, ReadOnlyList.from(ret()))))
.build();
final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend));
assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_MISSING, thrown.code());
assertTrue(thrown.getMessage().contains("qualified entrypoint"));
}
private static IRBackendExecutableFunction fn(
final String name,
final String moduleKey,
@ -248,6 +283,16 @@ class LowerToIRVMServiceTest {
final int moduleId,
final int callableId,
final ReadOnlyList<IRBackendExecutableFunction.Instruction> instructions) {
return fnWithModuleAndLocals(name, moduleKey, moduleId, callableId, 0, instructions);
}
private static IRBackendExecutableFunction fnWithModuleAndLocals(
final String name,
final String moduleKey,
final int moduleId,
final int callableId,
final int localSlots,
final ReadOnlyList<IRBackendExecutableFunction.Instruction> instructions) {
return new IRBackendExecutableFunction(
new FileId(0),
new ModuleId(moduleId),
@ -257,7 +302,7 @@ class LowerToIRVMServiceTest {
0,
10,
0,
0,
localSlots,
0,
4,
instructions,

View File

@ -23,6 +23,8 @@ public class IRBackend {
@Builder.Default
private final String entryPointCallableName = "main";
@Builder.Default
private final ModuleId entryPointModuleId = ModuleId.none();
@Builder.Default
private final ReadOnlyList<IRFunction> functions = ReadOnlyList.empty();
@Builder.Default
private final ReadOnlyList<IRBackendExecutableFunction> executableFunctions = ReadOnlyList.empty();
@ -41,6 +43,7 @@ public class IRBackend {
public static final class IRBackendAggregator {
private String entryPointCallableName;
private ModuleId entryPointModuleId = ModuleId.none();
private final ArrayList<IRFunction> functions = new ArrayList<>();
private final ArrayList<IRBackendExecutableFunction> executableFunctions = new ArrayList<>();
private final ModuleTable moduleTable = new ModuleTable();
@ -76,6 +79,13 @@ public class IRBackend {
return this;
}
public IRBackendAggregator entryPointModuleId(final ModuleId moduleId) {
if (moduleId != null) {
entryPointModuleId = moduleId;
}
return this;
}
private ModuleId[] reindexModules(final ReadOnlyList<ModuleReference> localModulePool) {
if (localModulePool == null || localModulePool.isEmpty()) {
return new ModuleId[0];
@ -273,6 +283,7 @@ public class IRBackend {
return IRBackend
.builder()
.entryPointCallableName(resolveEntryPointCallableName())
.entryPointModuleId(resolveEntryPointModuleId())
.functions(ReadOnlyList.wrap(functions))
.executableFunctions(ReadOnlyList.wrap(executableFunctions))
.modulePool(emitModulePool())
@ -291,12 +302,18 @@ public class IRBackend {
? "main"
: entryPointCallableName;
}
private ModuleId resolveEntryPointModuleId() {
return entryPointModuleId == null ? ModuleId.none() : entryPointModuleId;
}
}
@Override
public String toString() {
final var sb = new StringBuilder();
sb.append("IRBackend{entrypoint=").append(entryPointCallableName)
.append('@')
.append(entryPointModuleId == null || entryPointModuleId.isNone() ? "-" : entryPointModuleId.getIndex())
.append(", functions=").append(functions.size())
.append(", executableFunctions=").append(executableFunctions.size())
.append(", modulePool=").append(modulePool.size())

View File

@ -192,6 +192,18 @@ class IRBackendExecutableContractTest {
final var backend = aggregator.emit();
assertEquals("frame", backend.getEntryPointCallableName());
assertTrue(backend.getEntryPointModuleId().isNone());
}
@Test
void aggregatorMustEmitConfiguredEntrypointModuleId() {
final var aggregator = IRBackend.aggregator();
aggregator.entryPointCallableName("frame");
aggregator.entryPointModuleId(new ModuleId(3));
final var backend = aggregator.emit();
assertEquals("frame", backend.getEntryPointCallableName());
assertEquals(3, backend.getEntryPointModuleId().getIndex());
}
@Test