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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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