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.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -191,13 +192,26 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
} }
final var blockedModuleIds = blockedModulesByDependency(failedModuleIds, moduleDependencyGraph); final var blockedModuleIds = blockedModulesByDependency(failedModuleIds, moduleDependencyGraph);
final var entryPointCallableName = PBSDefinitions.PBS.getEntryPointCallableName();
final var entryPointModuleCandidates = new LinkedHashSet<ModuleId>();
for (final var compiledSource : compiledSourceFiles) { for (final var compiledSource : compiledSourceFiles) {
if (blockedModuleIds.contains(compiledSource.moduleId())) { if (blockedModuleIds.contains(compiledSource.moduleId())) {
continue; continue;
} }
irBackendAggregator.merge(compiledSource.irBackendFile()); 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(); final var irBackend = irBackendAggregator.emit();
logs.using(log).debug("PBS frontend lowered to IR BE:\n%s".formatted(irBackend)); 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, IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_DECLARATION_MISSING,
"frontend IRBackend entrypoint declaration is missing"); "frontend IRBackend entrypoint declaration is missing");
} }
final var entryPointModuleId = backend.getEntryPointModuleId();
final var sorted = new ArrayList<>(backend.getExecutableFunctions().asList()); final var sorted = new ArrayList<>(backend.getExecutableFunctions().asList());
sorted.sort((left, right) -> { sorted.sort((left, right) -> {
final var leftModuleKey = moduleSortKey(backend, left.moduleId(), left.moduleKey()); final var leftModuleKey = moduleSortKey(backend, left.moduleId(), left.moduleKey());
@ -314,15 +315,39 @@ public class LowerToIRVMService {
} }
return Integer.compare(left.sourceStart(), right.sourceStart()); return Integer.compare(left.sourceStart(), right.sourceStart());
}); });
final var entrypoints = sorted.stream() 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())) .filter(candidate -> entryPointCallableName.equals(candidate.callableName()))
.toList(); .toList();
}
if (entrypoints.isEmpty()) { 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( throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_MISSING, IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_MISSING,
"missing entrypoint callable '%s'".formatted(entryPointCallableName)); "missing entrypoint callable '%s'".formatted(entryPointCallableName));
} }
if (entrypoints.size() > 1) { 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( throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_AMBIGUOUS, IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_AMBIGUOUS,
"ambiguous entrypoint: found %d callables named '%s'".formatted( "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()); 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( private static IRBackendExecutableFunction fn(
final String name, final String name,
final String moduleKey, final String moduleKey,
@ -248,6 +283,16 @@ class LowerToIRVMServiceTest {
final int moduleId, final int moduleId,
final int callableId, final int callableId,
final ReadOnlyList<IRBackendExecutableFunction.Instruction> instructions) { 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( return new IRBackendExecutableFunction(
new FileId(0), new FileId(0),
new ModuleId(moduleId), new ModuleId(moduleId),
@ -257,7 +302,7 @@ class LowerToIRVMServiceTest {
0, 0,
10, 10,
0, 0,
0, localSlots,
0, 0,
4, 4,
instructions, instructions,

View File

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

View File

@ -192,6 +192,18 @@ class IRBackendExecutableContractTest {
final var backend = aggregator.emit(); final var backend = aggregator.emit();
assertEquals("frame", backend.getEntryPointCallableName()); 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 @Test