implements PR-09.3: enforce qualified entrypoint and moduleId-only ordering in LowerToIRVM

This commit is contained in:
bQUARKz 2026-03-09 16:10:07 +00:00
parent 5c69a02973
commit 8876a8fa35
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
7 changed files with 48 additions and 30 deletions

View File

@ -297,6 +297,11 @@ public class LowerToIRVMService {
"frontend IRBackend entrypoint declaration is missing");
}
final var entryPointModuleId = backend.getEntryPointModuleId();
if (entryPointModuleId == null || entryPointModuleId.isNone()) {
throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_DECLARATION_MISSING,
"frontend IRBackend qualified entrypoint declaration is missing entryPointModuleId");
}
final var sorted = new ArrayList<>(backend.getExecutableFunctions().asList());
sorted.sort((left, right) -> {
final var leftModuleKey = moduleSortKey(backend, left.moduleId());
@ -315,44 +320,25 @@ public class LowerToIRVMService {
}
return Integer.compare(left.sourceStart(), right.sourceStart());
});
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();
}
final var expectedModuleIndex = entryPointModuleId.getIndex();
final var entrypoints = sorted.stream()
.filter(candidate -> entryPointCallableName.equals(candidate.callableName()))
.filter(candidate -> !candidate.moduleId().isNone() && candidate.moduleId().getIndex() == expectedModuleIndex)
.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));
"missing qualified entrypoint callable '%s' for moduleId=%d".formatted(
entryPointCallableName,
entryPointModuleId.getIndex()));
}
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(
"ambiguous qualified entrypoint: found %d callables named '%s' in moduleId=%d".formatted(
entrypoints.size(),
entryPointCallableName));
entryPointCallableName,
entryPointModuleId.getIndex()));
}
final var entrypoint = entrypoints.getFirst();
final var ordered = new ArrayList<IRBackendExecutableFunction>(sorted.size());

View File

@ -43,8 +43,10 @@ class GoldenArtifactsTest {
private IRBackend fixtureBackend() {
return IRBackend.builder()
.entryPointModuleId(new p.studio.compiler.source.identifiers.ModuleId(0))
.executableFunctions(ReadOnlyList.from(new IRBackendExecutableFunction(
new FileId(1),
new p.studio.compiler.source.identifiers.ModuleId(0),
"main",
new CallableId(0),
0,

View File

@ -21,6 +21,7 @@ class LowerToIRVMServiceTest {
@Test
void lowerMustAssignEntrypointIdZeroAndSortRemainingDeterministically() {
final var backend = IRBackend.builder()
.entryPointModuleId(new ModuleId(0))
.executableFunctions(ReadOnlyList.from(
fn("aux", "app", 11, ReadOnlyList.from(
callFunc("app", "main", 10),
@ -42,6 +43,7 @@ class LowerToIRVMServiceTest {
void lowerMustUseFrontendDeclaredEntrypointCallable() {
final var backend = IRBackend.builder()
.entryPointCallableName("frame")
.entryPointModuleId(new ModuleId(0))
.executableFunctions(ReadOnlyList.from(
fn("aux", "app", 11, ReadOnlyList.from(
callFunc("app", "frame", 10),
@ -60,6 +62,7 @@ class LowerToIRVMServiceTest {
@Test
void lowerMustMapHostAndIntrinsicCallsites() {
final var backend = IRBackend.builder()
.entryPointModuleId(new ModuleId(0))
.executableFunctions(ReadOnlyList.from(
fn("main", "app", 10, ReadOnlyList.from(
callHost("gfx", "draw_pixel", 1, 0, 0),
@ -83,6 +86,7 @@ class LowerToIRVMServiceTest {
@Test
void lowerMustRejectUnterminatedFunction() {
final var backend = IRBackend.builder()
.entryPointModuleId(new ModuleId(0))
.executableFunctions(ReadOnlyList.from(
fn("main", "app", 10, ReadOnlyList.from(
callFunc("app", "main", 10)))))
@ -95,6 +99,7 @@ class LowerToIRVMServiceTest {
@Test
void lowerMustRejectMissingCallee() {
final var backend = IRBackend.builder()
.entryPointModuleId(new ModuleId(0))
.executableFunctions(ReadOnlyList.from(
fn("main", "app", 10, ReadOnlyList.from(
callFunc("app", "missing", 77),
@ -108,6 +113,7 @@ class LowerToIRVMServiceTest {
@Test
void lowerMustRejectUnknownCanonicalIntrinsicIdentity() {
final var backend = IRBackend.builder()
.entryPointModuleId(new ModuleId(0))
.executableFunctions(ReadOnlyList.from(
fn("main", "app", 10, ReadOnlyList.from(
callIntrinsic("core.color.pack", 1, 0),
@ -123,6 +129,7 @@ class LowerToIRVMServiceTest {
@Test
void lowerMustRejectCallArgSlotMismatch() {
final var backend = IRBackend.builder()
.entryPointModuleId(new ModuleId(0))
.executableFunctions(ReadOnlyList.from(
fn("callee", "app", 20, ReadOnlyList.from(ret())),
fn("main", "app", 10, ReadOnlyList.from(
@ -137,6 +144,7 @@ class LowerToIRVMServiceTest {
@Test
void lowerMustResolveJumpTargetsFromLabels() {
final var backend = IRBackend.builder()
.entryPointModuleId(new ModuleId(0))
.executableFunctions(ReadOnlyList.from(
fn("main", "app", 10, ReadOnlyList.from(
label("entry"),
@ -156,6 +164,7 @@ class LowerToIRVMServiceTest {
@Test
void lowerMustRejectMissingJumpTargetLabel() {
final var backend = IRBackend.builder()
.entryPointModuleId(new ModuleId(0))
.executableFunctions(ReadOnlyList.from(
fn("main", "app", 10, ReadOnlyList.from(
jmp("missing"),
@ -169,6 +178,7 @@ class LowerToIRVMServiceTest {
@Test
void lowerMustRejectWhenEntrypointIsMissing() {
final var backend = IRBackend.builder()
.entryPointModuleId(new ModuleId(0))
.executableFunctions(ReadOnlyList.from(
fn("helper", "app", 10, ReadOnlyList.from(ret()))))
.build();
@ -192,6 +202,7 @@ class LowerToIRVMServiceTest {
@Test
void lowerMustRejectWhenEntrypointIsAmbiguous() {
final var backend = IRBackend.builder()
.entryPointModuleId(new ModuleId(0))
.executableFunctions(ReadOnlyList.from(
fn("main", "app", 10, ReadOnlyList.from(ret())),
fn("main", "sdk", 11, ReadOnlyList.from(ret()))))
@ -205,6 +216,7 @@ class LowerToIRVMServiceTest {
void lowerMustUseModulePoolCanonicalIdentityForNonEntrypointOrdering() {
final var backend = IRBackend.builder()
.entryPointCallableName("frame")
.entryPointModuleId(new ModuleId(1))
.modulePool(ReadOnlyList.from(
new ModuleReference("app", ReadOnlyList.from("alpha")),
new ModuleReference("app", ReadOnlyList.from("zeta"))))
@ -264,6 +276,7 @@ class LowerToIRVMServiceTest {
final ReadOnlyList<IRBackendExecutableFunction.Instruction> instructions) {
return new IRBackendExecutableFunction(
new FileId(0),
new ModuleId(0),
name,
new CallableId(callableId),
0,

View File

@ -28,6 +28,7 @@ class OptimizeIRVMEquivalenceHarnessTest {
@Test
void optimizeOnOffMustPreserveObservableTraceForLoweredHostIntrinsicFixture() {
final var backend = IRBackend.builder()
.entryPointModuleId(new p.studio.compiler.source.identifiers.ModuleId(0))
.executableFunctions(ReadOnlyList.from(
fn("main", 1, ReadOnlyList.from(
callHost("gfx", "draw_pixel", 1, 0, 0),
@ -322,6 +323,7 @@ class OptimizeIRVMEquivalenceHarnessTest {
final ReadOnlyList<IRBackendExecutableFunction.Instruction> instructions) {
return new IRBackendExecutableFunction(
new FileId(1),
new p.studio.compiler.source.identifiers.ModuleId(0),
name,
new CallableId(callableId),
0,

View File

@ -178,6 +178,7 @@ class BackendGateIIntegrationTest {
final IRBackendExecutableFunction function,
final ReadOnlyList<IntrinsicReference> intrinsicPool) {
return IRBackend.builder()
.entryPointModuleId(new p.studio.compiler.source.identifiers.ModuleId(0))
.executableFunctions(ReadOnlyList.from(function))
.intrinsicPool(intrinsicPool)
.build();
@ -188,6 +189,7 @@ class BackendGateIIntegrationTest {
final ReadOnlyList<IRBackendExecutableFunction.Instruction> instructions) {
return new IRBackendExecutableFunction(
new FileId(1),
new p.studio.compiler.source.identifiers.ModuleId(0),
name,
new CallableId(1),
0,

View File

@ -26,8 +26,10 @@ class BackendSafetyGateSUTest {
@Test
void lowerStageMustExposeDeterministicFailureCodeForSameInvalidInput() {
final var backend = IRBackend.builder()
.entryPointModuleId(new p.studio.compiler.source.identifiers.ModuleId(0))
.executableFunctions(ReadOnlyList.from(new IRBackendExecutableFunction(
new FileId(1),
new p.studio.compiler.source.identifiers.ModuleId(0),
"main",
new CallableId(1),
0,
@ -109,8 +111,10 @@ class BackendSafetyGateSUTest {
@Test
void fullPipelineMustProduceDeterministicBytecodeForSameInput() {
final var backend = IRBackend.builder()
.entryPointModuleId(new p.studio.compiler.source.identifiers.ModuleId(0))
.executableFunctions(ReadOnlyList.from(new IRBackendExecutableFunction(
new FileId(1),
new p.studio.compiler.source.identifiers.ModuleId(0),
"main",
new CallableId(1),
0,

View File

@ -36,8 +36,10 @@ class LowerToIRVMPipelineStageTest {
void runMustLowerExecutableFunctionsToIrvm() {
final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, "."));
ctx.irBackend = IRBackend.builder()
.entryPointModuleId(new p.studio.compiler.source.identifiers.ModuleId(0))
.executableFunctions(ReadOnlyList.from(new IRBackendExecutableFunction(
new FileId(0),
new p.studio.compiler.source.identifiers.ModuleId(0),
"main",
new CallableId(1),
0,
@ -67,8 +69,10 @@ class LowerToIRVMPipelineStageTest {
void runMustPropagateConfiguredVmProfileToLowering() {
final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, ".", "experimental-v1"));
ctx.irBackend = IRBackend.builder()
.entryPointModuleId(new p.studio.compiler.source.identifiers.ModuleId(0))
.executableFunctions(ReadOnlyList.from(new IRBackendExecutableFunction(
new FileId(0),
new p.studio.compiler.source.identifiers.ModuleId(0),
"main",
new CallableId(1),
0,
@ -98,8 +102,10 @@ class LowerToIRVMPipelineStageTest {
final var callSpan = new Span(new FileId(9), 12, 24);
final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, "."));
ctx.irBackend = IRBackend.builder()
.entryPointModuleId(new p.studio.compiler.source.identifiers.ModuleId(0))
.executableFunctions(ReadOnlyList.from(new IRBackendExecutableFunction(
new FileId(9),
new p.studio.compiler.source.identifiers.ModuleId(0),
"main",
new CallableId(1),
10,
@ -140,9 +146,11 @@ class LowerToIRVMPipelineStageTest {
void runMustRejectCallWhenStackDoesNotProvideDeclaredArgs() {
final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, "."));
ctx.irBackend = IRBackend.builder()
.entryPointModuleId(new p.studio.compiler.source.identifiers.ModuleId(0))
.executableFunctions(ReadOnlyList.from(
new IRBackendExecutableFunction(
new FileId(0),
new p.studio.compiler.source.identifiers.ModuleId(0),
"callee",
new CallableId(2),
0,
@ -160,6 +168,7 @@ class LowerToIRVMPipelineStageTest {
Span.none()),
new IRBackendExecutableFunction(
new FileId(0),
new p.studio.compiler.source.identifiers.ModuleId(0),
"main",
new CallableId(1),
0,