diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java index c976b4bd..2e7e69f2 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java @@ -19,6 +19,7 @@ import p.studio.compiler.source.identifiers.CallableShapeId; import p.studio.compiler.source.identifiers.CallableId; import p.studio.compiler.source.identifiers.FileId; import p.studio.compiler.source.identifiers.IntrinsicId; +import p.studio.compiler.source.identifiers.ModuleId; import p.studio.compiler.source.identifiers.NameId; import p.studio.compiler.source.identifiers.TypeSurfaceId; import p.studio.compiler.source.tables.CallableShapeTable; @@ -26,6 +27,7 @@ import p.studio.compiler.source.tables.CallableSignatureRef; import p.studio.compiler.source.tables.CallableTable; import p.studio.compiler.source.tables.IntrinsicReference; import p.studio.compiler.source.tables.IntrinsicTable; +import p.studio.compiler.source.tables.ModuleReference; import p.studio.compiler.source.tables.NameTable; import p.studio.compiler.source.tables.TypeSurfaceTable; import p.studio.utilities.structures.ReadOnlyList; @@ -71,7 +73,16 @@ public final class PbsFrontendCompiler { ? PbsParser.ParseMode.INTERFACE_MODULE : PbsParser.ParseMode.ORDINARY; final var ast = PbsParser.parse(tokens, fileId, diagnostics, parseMode); - final var irBackendFile = compileParsedFile(fileId, ast, diagnostics, sourceKind, "", hostAdmissionContext, nameTable); + final var irBackendFile = compileParsedFile( + fileId, + ast, + diagnostics, + sourceKind, + ModuleId.none(), + "", + ReadOnlyList.empty(), + hostAdmissionContext, + nameTable); if (diagnostics.errorCount() > admissionBaseline) { return IRBackendFile.empty(fileId); } @@ -99,7 +110,16 @@ public final class PbsFrontendCompiler { final DiagnosticSink diagnostics, final SourceKind sourceKind, final HostAdmissionContext hostAdmissionContext) { - return compileParsedFile(fileId, ast, diagnostics, sourceKind, "", hostAdmissionContext, new NameTable()); + return compileParsedFile( + fileId, + ast, + diagnostics, + sourceKind, + ModuleId.none(), + "", + ReadOnlyList.empty(), + hostAdmissionContext, + new NameTable()); } public IRBackendFile compileParsedFile( @@ -109,7 +129,16 @@ public final class PbsFrontendCompiler { final SourceKind sourceKind, final String moduleKey, final HostAdmissionContext hostAdmissionContext) { - return compileParsedFile(fileId, ast, diagnostics, sourceKind, moduleKey, hostAdmissionContext, new NameTable()); + return compileParsedFile( + fileId, + ast, + diagnostics, + sourceKind, + ModuleId.none(), + moduleKey, + ReadOnlyList.empty(), + hostAdmissionContext, + new NameTable()); } public IRBackendFile compileParsedFile( @@ -117,7 +146,9 @@ public final class PbsFrontendCompiler { final PbsAst.File ast, final DiagnosticSink diagnostics, final SourceKind sourceKind, + final ModuleId moduleId, final String moduleKey, + final ReadOnlyList modulePool, final HostAdmissionContext hostAdmissionContext, final NameTable nameTable) { return compileParsedFile( @@ -125,7 +156,9 @@ public final class PbsFrontendCompiler { ast, diagnostics, sourceKind, + moduleId, moduleKey, + modulePool, hostAdmissionContext, nameTable, ReadOnlyList.empty(), @@ -138,12 +171,16 @@ public final class PbsFrontendCompiler { final PbsAst.File ast, final DiagnosticSink diagnostics, final SourceKind sourceKind, + final ModuleId moduleId, final String moduleKey, + final ReadOnlyList modulePool, final HostAdmissionContext hostAdmissionContext, final NameTable nameTable, final ReadOnlyList supplementalTopDecls, final ReadOnlyList importedCallables, final IRReservedMetadata importedReservedMetadata) { + final var effectiveModuleId = moduleId == null ? ModuleId.none() : moduleId; + final var effectiveModulePool = modulePool == null ? ReadOnlyList.empty() : modulePool; final var effectiveNameTable = nameTable == null ? new NameTable() : nameTable; final var effectiveSupplementalTopDecls = supplementalTopDecls == null ? ReadOnlyList.empty() @@ -187,6 +224,7 @@ public final class PbsFrontendCompiler { fileId, ast, effectiveSupplementalTopDecls, + effectiveModuleId, moduleKey, effectiveReservedMetadata, effectiveNameTable, @@ -200,6 +238,7 @@ public final class PbsFrontendCompiler { functions, executableLowering.executableFunctions(), reservedMetadata, + effectiveModulePool, executableLowering.callableSignatures(), executableLowering.intrinsicPool()); } @@ -221,12 +260,14 @@ public final class PbsFrontendCompiler { final FileId fileId, final PbsAst.File ast, final ReadOnlyList supplementalTopDecls, + final ModuleId moduleId, final String moduleKey, final IRReservedMetadata reservedMetadata, final NameTable nameTable, final DiagnosticSink diagnostics, final ReadOnlyList importedCallables) { final var normalizedModuleKey = moduleKey == null ? "" : moduleKey; + final var normalizedModuleId = moduleId == null ? ModuleId.none() : moduleId; final var hostByMethodName = new HashMap>(); for (final var hostBinding : reservedMetadata.hostMethodBindings()) { hostByMethodName @@ -318,6 +359,7 @@ public final class PbsFrontendCompiler { final var declaredFn = declaredCallable.functionDecl(); final var callableShapeId = callableShapeId(declaredFn, typeSurfaceTable, callableShapeTable); final var callableId = callableIdTable.register( + normalizedModuleId, normalizedModuleKey, declaredCallable.callableName(), declaredFn.parameters().size(), @@ -333,6 +375,7 @@ public final class PbsFrontendCompiler { } for (final var importedCallable : importedCallables) { final var callableId = callableIdTable.register( + importedCallable.moduleId(), importedCallable.moduleKey(), importedCallable.callableName(), importedCallable.arity(), @@ -359,6 +402,7 @@ public final class PbsFrontendCompiler { localSlotByNameId.put(nameTable.register(fn.parameters().get(paramIndex).name()), paramIndex); } final var loweringContext = new ExecutableLoweringContext( + normalizedModuleId, normalizedModuleKey, diagnostics, nameTable, @@ -401,6 +445,7 @@ public final class PbsFrontendCompiler { final var end = safeToInt(fn.span().getEnd()); executableFunctions.add(new IRBackendExecutableFunction( fileId, + normalizedModuleId, normalizedModuleKey, callable.callableName(), functionCallableId, @@ -921,6 +966,7 @@ public final class PbsFrontendCompiler { final var calleeCallableId = callableCandidates.getFirst(); context.instructions().add(new IRBackendExecutableFunction.Instruction( IRBackendExecutableFunction.InstructionKind.CALL_FUNC, + context.moduleId(), context.moduleKey(), calleeIdentity.primaryCallableDisplayName(), calleeCallableId, @@ -1292,6 +1338,7 @@ public final class PbsFrontendCompiler { } private static final class ExecutableLoweringContext { + private final ModuleId moduleId; private final String moduleKey; private final DiagnosticSink diagnostics; private final NameTable nameTable; @@ -1308,6 +1355,7 @@ public final class PbsFrontendCompiler { private int nextLabelId = 0; private ExecutableLoweringContext( + final ModuleId moduleId, final String moduleKey, final DiagnosticSink diagnostics, final NameTable nameTable, @@ -1319,6 +1367,7 @@ public final class PbsFrontendCompiler { final Map returnSlotsByCallableId, final IntrinsicTable intrinsicIdTable, final Map localSlotByNameId) { + this.moduleId = moduleId == null ? ModuleId.none() : moduleId; this.moduleKey = moduleKey; this.diagnostics = diagnostics; this.nameTable = nameTable; @@ -1332,6 +1381,10 @@ public final class PbsFrontendCompiler { this.localSlotByNameId = localSlotByNameId == null ? Map.of() : localSlotByNameId; } + private ModuleId moduleId() { + return moduleId; + } + private String moduleKey() { return moduleKey; } @@ -1440,11 +1493,15 @@ public final class PbsFrontendCompiler { } public record ImportedCallableSurface( + ModuleId moduleId, String moduleKey, String callableName, int arity, int returnSlots, String shapeSurface) { + public ImportedCallableSurface { + moduleId = moduleId == null ? ModuleId.none() : moduleId; + } } private static final class ExecutableLoweringAnalysisException extends RuntimeException { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java index 95b51a65..8f9dc61e 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java @@ -159,6 +159,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { markModulesWithLinkingErrors(diagnostics, moduleIdByFile, failedModuleIds); final var moduleDependencyGraph = buildModuleDependencyGraph(parsedSourceFiles, moduleTable); final var importedSemanticContexts = buildImportedSemanticContexts(parsedSourceFiles, moduleTable); + final var canonicalModulePool = emitModulePool(moduleTable); final var compiledSourceFiles = new ArrayList(parsedSourceFiles.size()); for (final var parsedSource : parsedSourceFiles) { @@ -175,7 +176,9 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { parsedSource.ast(), diagnostics, parsedSource.sourceKind(), + parsedSource.moduleId(), renderModuleKey(moduleTable, parsedSource.moduleId()), + canonicalModulePool, ctx.hostAdmissionContext(), nameTable, importedSemanticContext.supplementalTopDecls(), @@ -431,6 +434,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { importedCallables, importedCallableKeys, new PbsFrontendCompiler.ImportedCallableSurface( + importedModuleId, importedModuleKey, localName + "." + method.name(), method.parameters().size(), @@ -444,6 +448,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { importedCallables, importedCallableKeys, new PbsFrontendCompiler.ImportedCallableSurface( + importedModuleId, importedModuleKey, localName, functionDecl.parameters().size(), @@ -793,6 +798,14 @@ public class PBSFrontendPhaseService implements FrontendPhaseService { return moduleReference.project() + ":" + String.join("/", moduleReference.pathSegments().asList()); } + private ReadOnlyList emitModulePool(final ModuleTable moduleTable) { + final var pool = new ArrayList(moduleTable.size()); + for (final var moduleId : moduleTable.identifiers()) { + pool.add(moduleTable.get(moduleId)); + } + return ReadOnlyList.wrap(pool); + } + private static final class MutableModuleUnit { private final ArrayList sources = new ArrayList<>(); private final ArrayList barrels = new ArrayList<>(); diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java index 39ce8235..94ea078c 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java @@ -6,19 +6,15 @@ import p.studio.compiler.models.IRBackend; import p.studio.compiler.models.IRBackendExecutableFunction; import p.studio.compiler.source.Span; import p.studio.compiler.source.identifiers.CallableId; +import p.studio.compiler.source.identifiers.ModuleId; +import p.studio.compiler.source.tables.ModuleReference; import p.studio.utilities.structures.ReadOnlyList; import java.util.ArrayList; -import java.util.Comparator; import java.util.HashMap; +import java.util.StringJoiner; public class LowerToIRVMService { - private static final Comparator FUNCTION_ORDER = Comparator - .comparing(IRBackendExecutableFunction::moduleKey) - .thenComparing(IRBackendExecutableFunction::callableName) - .thenComparingInt(function -> function.callableId().getIndex()) - .thenComparingInt(IRBackendExecutableFunction::sourceStart); - private final IRVMValidator validator; public LowerToIRVMService() { @@ -301,7 +297,23 @@ public class LowerToIRVMService { "frontend IRBackend entrypoint declaration is missing"); } final var sorted = new ArrayList<>(backend.getExecutableFunctions().asList()); - sorted.sort(FUNCTION_ORDER); + sorted.sort((left, right) -> { + final var leftModuleKey = moduleSortKey(backend, left.moduleId(), left.moduleKey()); + final var rightModuleKey = moduleSortKey(backend, right.moduleId(), right.moduleKey()); + final var moduleCmp = leftModuleKey.compareTo(rightModuleKey); + if (moduleCmp != 0) { + return moduleCmp; + } + final var nameCmp = left.callableName().compareTo(right.callableName()); + if (nameCmp != 0) { + return nameCmp; + } + final var idCmp = Integer.compare(left.callableId().getIndex(), right.callableId().getIndex()); + if (idCmp != 0) { + return idCmp; + } + return Integer.compare(left.sourceStart(), right.sourceStart()); + }); final var entrypoints = sorted.stream() .filter(candidate -> entryPointCallableName.equals(candidate.callableName())) .toList(); @@ -328,6 +340,27 @@ public class LowerToIRVMService { return ReadOnlyList.wrap(ordered); } + private String moduleSortKey( + final IRBackend backend, + final ModuleId moduleId, + final String fallbackModuleKey) { + if (moduleId != null && !moduleId.isNone()) { + final var index = moduleId.getIndex(); + if (index >= 0 && index < backend.getModulePool().size()) { + return canonicalModuleSortKey(backend.getModulePool().get(index)); + } + } + return fallbackModuleKey == null ? "" : fallbackModuleKey; + } + + private String canonicalModuleSortKey(final ModuleReference reference) { + final var joiner = new StringJoiner("/", reference.project() + ":", ""); + for (final var segment : reference.pathSegments()) { + joiner.add(segment); + } + return joiner.toString(); + } + private BytecodeModule.SourceSpan toBytecodeSpan( final int fallbackFileId, final Span span) { diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java index a2e90568..3ca21b6c 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java @@ -7,7 +7,9 @@ import p.studio.compiler.source.Span; import p.studio.compiler.source.identifiers.CallableId; import p.studio.compiler.source.identifiers.FileId; import p.studio.compiler.source.identifiers.IntrinsicId; +import p.studio.compiler.source.identifiers.ModuleId; import p.studio.compiler.source.tables.IntrinsicReference; +import p.studio.compiler.source.tables.ModuleReference; import p.studio.utilities.structures.ReadOnlyList; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -199,6 +201,27 @@ class LowerToIRVMServiceTest { assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_AMBIGUOUS, thrown.code()); } + @Test + void lowerMustUseModulePoolCanonicalIdentityForNonEntrypointOrdering() { + final var backend = IRBackend.builder() + .entryPointCallableName("frame") + .modulePool(ReadOnlyList.from( + new ModuleReference("app", ReadOnlyList.from("alpha")), + new ModuleReference("app", ReadOnlyList.from("zeta")))) + .executableFunctions(ReadOnlyList.from( + fnWithModule("helper_in_zeta", "legacy/zeta", 1, 21, ReadOnlyList.from(ret())), + fnWithModule("frame", "legacy/zeta", 1, 20, ReadOnlyList.from(ret())), + fnWithModule("helper_in_alpha", "legacy/alpha", 0, 22, ReadOnlyList.from(ret())))) + .build(); + + final var lowered = new LowerToIRVMService().lower(backend); + + assertEquals(3, lowered.module().functions().size()); + assertEquals("frame", lowered.module().functions().get(0).name()); + assertEquals("helper_in_alpha", lowered.module().functions().get(1).name()); + assertEquals("helper_in_zeta", lowered.module().functions().get(2).name()); + } + private static IRBackendExecutableFunction fn( final String name, final String moduleKey, @@ -219,6 +242,28 @@ class LowerToIRVMServiceTest { Span.none()); } + private static IRBackendExecutableFunction fnWithModule( + final String name, + final String moduleKey, + final int moduleId, + final int callableId, + final ReadOnlyList instructions) { + return new IRBackendExecutableFunction( + new FileId(0), + new ModuleId(moduleId), + moduleKey, + name, + new CallableId(callableId), + 0, + 10, + 0, + 0, + 0, + 4, + instructions, + Span.none()); + } + private static IRBackendExecutableFunction.Instruction ret() { return new IRBackendExecutableFunction.Instruction( IRBackendExecutableFunction.InstructionKind.RET, diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/ModuleId.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/ModuleId.java index 4f4f8964..86bacadb 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/ModuleId.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/ModuleId.java @@ -1,7 +1,17 @@ package p.studio.compiler.source.identifiers; public class ModuleId extends SourceIdentifier { + public static final ModuleId NONE = new ModuleId(-1); + public ModuleId(int id) { super(id); } + + public static ModuleId none() { + return NONE; + } + + public boolean isNone() { + return this == NONE; + } } diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableSignatureRef.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableSignatureRef.java index 95932d34..5afcd8f4 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableSignatureRef.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableSignatureRef.java @@ -1,14 +1,29 @@ package p.studio.compiler.source.tables; +import p.studio.compiler.source.identifiers.ModuleId; + import java.util.Objects; public record CallableSignatureRef( + ModuleId moduleId, String moduleKey, String callableName, int arity, String typeShape) { + public CallableSignatureRef( + final String moduleKey, + final String callableName, + final int arity, + final String typeShape) { + this(ModuleId.none(), moduleKey, callableName, arity, typeShape); + } + public CallableSignatureRef { + moduleId = moduleId == null ? ModuleId.none() : moduleId; + if (!moduleId.isNone()) { + moduleId.getIndex(); + } moduleKey = moduleKey == null ? "" : moduleKey; callableName = Objects.requireNonNull(callableName, "callableName"); typeShape = typeShape == null ? "" : typeShape; diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableTable.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableTable.java index 659d6f74..ce8d8a52 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableTable.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableTable.java @@ -1,6 +1,7 @@ package p.studio.compiler.source.tables; import p.studio.compiler.source.identifiers.CallableId; +import p.studio.compiler.source.identifiers.ModuleId; public class CallableTable extends InternTable implements CallableTableReader { @@ -13,6 +14,15 @@ public class CallableTable extends InternTable final String callableName, final int arity, final String typeShape) { - return register(new CallableSignatureRef(moduleKey, callableName, arity, typeShape)); + return register(new CallableSignatureRef(ModuleId.none(), moduleKey, callableName, arity, typeShape)); + } + + public CallableId register( + final ModuleId moduleId, + final String moduleKey, + final String callableName, + final int arity, + final String typeShape) { + return register(new CallableSignatureRef(moduleId, moduleKey, callableName, arity, typeShape)); } } diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend.java index 0e7e6ef1..5ea92a83 100644 --- a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend.java +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackend.java @@ -4,14 +4,18 @@ import lombok.Builder; import lombok.Getter; import p.studio.compiler.source.identifiers.CallableId; import p.studio.compiler.source.identifiers.IntrinsicId; +import p.studio.compiler.source.identifiers.ModuleId; import p.studio.compiler.source.tables.CallableSignatureRef; import p.studio.compiler.source.tables.CallableTable; import p.studio.compiler.source.tables.IntrinsicReference; import p.studio.compiler.source.tables.IntrinsicTable; +import p.studio.compiler.source.tables.ModuleReference; +import p.studio.compiler.source.tables.ModuleTable; import p.studio.utilities.structures.ReadOnlyList; import java.util.ArrayList; import java.util.LinkedHashSet; +import java.util.List; @Builder @Getter @@ -23,6 +27,8 @@ public class IRBackend { @Builder.Default private final ReadOnlyList executableFunctions = ReadOnlyList.empty(); @Builder.Default + private final ReadOnlyList modulePool = ReadOnlyList.empty(); + @Builder.Default private final ReadOnlyList callableSignatures = ReadOnlyList.empty(); @Builder.Default private final ReadOnlyList intrinsicPool = ReadOnlyList.empty(); @@ -37,6 +43,7 @@ public class IRBackend { private String entryPointCallableName; private final ArrayList functions = new ArrayList<>(); private final ArrayList executableFunctions = new ArrayList<>(); + private final ModuleTable moduleTable = new ModuleTable(); private final CallableTable callableTable = new CallableTable(); private final IntrinsicTable intrinsicTable = new IntrinsicTable(); private final ArrayList hostMethodBindings = new ArrayList<>(); @@ -49,10 +56,11 @@ public class IRBackend { return; } functions.addAll(backendFile.functions().asList()); - final var callableRemap = reindexCallables(backendFile.callableSignatures()); + final var moduleRemap = reindexModules(backendFile.modulePool()); + final var callableRemap = reindexCallables(backendFile.callableSignatures(), moduleRemap); final var intrinsicRemap = reindexIntrinsics(backendFile.intrinsicPool()); for (final var function : backendFile.executableFunctions()) { - executableFunctions.add(remapExecutableFunction(function, callableRemap, intrinsicRemap)); + executableFunctions.add(remapExecutableFunction(function, moduleRemap, callableRemap, intrinsicRemap)); } final var metadata = backendFile.reservedMetadata(); hostMethodBindings.addAll(metadata.hostMethodBindings().asList()); @@ -68,22 +76,52 @@ public class IRBackend { return this; } - private CallableId[] reindexCallables(final ReadOnlyList localCallableSignatures) { + private ModuleId[] reindexModules(final ReadOnlyList localModulePool) { + if (localModulePool == null || localModulePool.isEmpty()) { + return new ModuleId[0]; + } + final var remap = new ModuleId[localModulePool.size()]; + for (var i = 0; i < localModulePool.size(); i++) { + remap[i] = moduleTable.register(localModulePool.get(i)); + } + return remap; + } + + private CallableId[] reindexCallables( + final ReadOnlyList localCallableSignatures, + final ModuleId[] moduleRemap) { if (localCallableSignatures == null || localCallableSignatures.isEmpty()) { return new CallableId[0]; } final var remap = new CallableId[localCallableSignatures.size()]; for (var i = 0; i < localCallableSignatures.size(); i++) { - remap[i] = callableTable.register(localCallableSignatures.get(i)); + final var signature = localCallableSignatures.get(i); + final var remappedModuleId = remapModuleId( + signature.moduleId(), + signature.moduleKey(), + moduleRemap, + "callable signature"); + remap[i] = callableTable.register( + remappedModuleId, + signature.moduleKey(), + signature.callableName(), + signature.arity(), + signature.typeShape()); } return remap; } private IRBackendExecutableFunction remapExecutableFunction( final IRBackendExecutableFunction function, + final ModuleId[] moduleRemap, final CallableId[] callableRemap, final IntrinsicId[] intrinsicRemap) { final var remappedInstructions = new ArrayList(function.instructions().size()); + final var remappedFunctionModuleId = remapModuleId( + function.moduleId(), + function.moduleKey(), + moduleRemap, + "function"); for (final var instruction : function.instructions()) { final CallableId remappedCallee; if (instruction.calleeCallableId() == null) { @@ -101,8 +139,14 @@ public class IRBackend { } else { remappedIntrinsic = instruction.intrinsicCall(); } + final var remappedCalleeModuleId = remapModuleId( + instruction.calleeModuleId(), + instruction.calleeModuleKey(), + moduleRemap, + "callee"); remappedInstructions.add(new IRBackendExecutableFunction.Instruction( instruction.kind(), + remappedCalleeModuleId, instruction.calleeModuleKey(), instruction.calleeCallableName(), remappedCallee, @@ -116,6 +160,7 @@ public class IRBackend { } return new IRBackendExecutableFunction( function.fileId(), + remappedFunctionModuleId, function.moduleKey(), function.callableName(), remapCallableId(function.callableId(), callableRemap, "function"), @@ -129,6 +174,45 @@ public class IRBackend { function.span()); } + private ModuleId remapModuleId( + final ModuleId localModuleId, + final String moduleKey, + final ModuleId[] moduleRemap, + final String label) { + if (localModuleId != null && !localModuleId.isNone()) { + final var localIndex = localModuleId.getIndex(); + if (localIndex < 0 || localIndex >= moduleRemap.length) { + throw new IllegalArgumentException("invalid local " + label + " module id: " + localModuleId); + } + return moduleRemap[localIndex]; + } + final var fallbackReference = moduleReferenceFromKey(moduleKey); + if (fallbackReference != null) { + return moduleTable.register(fallbackReference); + } + return ModuleId.none(); + } + + private ModuleReference moduleReferenceFromKey(final String moduleKey) { + if (moduleKey == null || moduleKey.isBlank()) { + return null; + } + final var separator = moduleKey.indexOf(':'); + if (separator <= 0) { + return null; + } + final var project = moduleKey.substring(0, separator).trim(); + final var rawPath = moduleKey.substring(separator + 1).trim(); + if (project.isBlank()) { + return null; + } + if (rawPath.isBlank()) { + return new ModuleReference(project, ReadOnlyList.empty()); + } + final var segments = List.of(rawPath.split("/")); + return new ModuleReference(project, ReadOnlyList.wrap(segments)); + } + private CallableId remapCallableId( final CallableId localCallableId, final CallableId[] callableRemap, @@ -169,6 +253,14 @@ public class IRBackend { return ReadOnlyList.wrap(signatures); } + private ReadOnlyList emitModulePool() { + final var modules = new ArrayList(moduleTable.size()); + for (final var moduleId : moduleTable.identifiers()) { + modules.add(moduleTable.get(moduleId)); + } + return ReadOnlyList.wrap(modules); + } + private ReadOnlyList emitIntrinsicPool() { final var pool = new ArrayList(intrinsicTable.size()); for (final var intrinsicId : intrinsicTable.identifiers()) { @@ -183,6 +275,7 @@ public class IRBackend { .entryPointCallableName(resolveEntryPointCallableName()) .functions(ReadOnlyList.wrap(functions)) .executableFunctions(ReadOnlyList.wrap(executableFunctions)) + .modulePool(emitModulePool()) .callableSignatures(emitCallableSignatures()) .intrinsicPool(emitIntrinsicPool()) .reservedMetadata(new IRReservedMetadata( @@ -206,6 +299,7 @@ public class IRBackend { sb.append("IRBackend{entrypoint=").append(entryPointCallableName) .append(", functions=").append(functions.size()) .append(", executableFunctions=").append(executableFunctions.size()) + .append(", modulePool=").append(modulePool.size()) .append(", callableSignatures=").append(callableSignatures.size()) .append(", intrinsicPool=").append(intrinsicPool.size()) .append(", hostBindings=").append(reservedMetadata.hostMethodBindings().size()) diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendExecutableFunction.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendExecutableFunction.java index ce4d3388..30a9cc96 100644 --- a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendExecutableFunction.java +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendExecutableFunction.java @@ -4,12 +4,14 @@ import p.studio.compiler.source.Span; import p.studio.compiler.source.identifiers.CallableId; import p.studio.compiler.source.identifiers.FileId; import p.studio.compiler.source.identifiers.IntrinsicId; +import p.studio.compiler.source.identifiers.ModuleId; import p.studio.utilities.structures.ReadOnlyList; import java.util.Objects; public record IRBackendExecutableFunction( FileId fileId, + ModuleId moduleId, String moduleKey, String callableName, CallableId callableId, @@ -22,8 +24,41 @@ public record IRBackendExecutableFunction( ReadOnlyList instructions, Span span) { + public IRBackendExecutableFunction( + final FileId fileId, + final String moduleKey, + final String callableName, + final CallableId callableId, + final int sourceStart, + final int sourceEnd, + final int paramSlots, + final int localSlots, + final int returnSlots, + final int maxStackSlots, + final ReadOnlyList instructions, + final Span span) { + this( + fileId, + ModuleId.none(), + moduleKey, + callableName, + callableId, + sourceStart, + sourceEnd, + paramSlots, + localSlots, + returnSlots, + maxStackSlots, + instructions, + span); + } + public IRBackendExecutableFunction { fileId = Objects.requireNonNull(fileId, "fileId"); + moduleId = moduleId == null ? ModuleId.none() : moduleId; + if (!moduleId.isNone()) { + moduleId.getIndex(); + } moduleKey = moduleKey == null ? "" : moduleKey; callableName = Objects.requireNonNull(callableName, "callableName"); if (callableName.isBlank()) { @@ -52,6 +87,7 @@ public record IRBackendExecutableFunction( public record Instruction( InstructionKind kind, + ModuleId calleeModuleId, String calleeModuleKey, String calleeCallableName, CallableId calleeCallableId, @@ -64,13 +100,78 @@ public record IRBackendExecutableFunction( Span span) { public Instruction( final InstructionKind kind, + final ModuleId calleeModuleId, final String calleeModuleKey, final String calleeCallableName, final CallableId calleeCallableId, final HostCallMetadata hostCall, final IntrinsicCallMetadata intrinsicCall, final Span span) { - this(kind, calleeModuleKey, calleeCallableName, calleeCallableId, hostCall, intrinsicCall, null, null, null, null, span); + this(kind, calleeModuleId, calleeModuleKey, calleeCallableName, calleeCallableId, hostCall, intrinsicCall, null, null, null, null, span); + } + + public Instruction( + final InstructionKind kind, + final ModuleId calleeModuleId, + final String calleeModuleKey, + final String calleeCallableName, + final CallableId calleeCallableId, + final HostCallMetadata hostCall, + final IntrinsicCallMetadata intrinsicCall, + final Integer expectedArgSlots, + final Integer expectedRetSlots, + final Span span) { + this(kind, calleeModuleId, calleeModuleKey, calleeCallableName, calleeCallableId, hostCall, intrinsicCall, null, null, expectedArgSlots, expectedRetSlots, span); + } + + public Instruction( + final InstructionKind kind, + final ModuleId calleeModuleId, + final String calleeModuleKey, + final String calleeCallableName, + final HostCallMetadata hostCall, + final IntrinsicCallMetadata intrinsicCall, + final Span span) { + this(kind, calleeModuleId, calleeModuleKey, calleeCallableName, null, hostCall, intrinsicCall, null, null, null, null, span); + } + + public Instruction( + final InstructionKind kind, + final ModuleId calleeModuleId, + final String calleeModuleKey, + final String calleeCallableName, + final HostCallMetadata hostCall, + final IntrinsicCallMetadata intrinsicCall, + final Integer expectedArgSlots, + final Integer expectedRetSlots, + final Span span) { + this(kind, calleeModuleId, calleeModuleKey, calleeCallableName, null, hostCall, intrinsicCall, null, null, expectedArgSlots, expectedRetSlots, span); + } + + public Instruction( + final InstructionKind kind, + final String calleeModuleKey, + final String calleeCallableName, + final CallableId calleeCallableId, + final HostCallMetadata hostCall, + final IntrinsicCallMetadata intrinsicCall, + final String label, + final String targetLabel, + final Integer expectedArgSlots, + final Integer expectedRetSlots, + final Span span) { + this(kind, ModuleId.none(), calleeModuleKey, calleeCallableName, calleeCallableId, hostCall, intrinsicCall, label, targetLabel, expectedArgSlots, expectedRetSlots, span); + } + + public Instruction( + final InstructionKind kind, + final String calleeModuleKey, + final String calleeCallableName, + final CallableId calleeCallableId, + final HostCallMetadata hostCall, + final IntrinsicCallMetadata intrinsicCall, + final Span span) { + this(kind, ModuleId.none(), calleeModuleKey, calleeCallableName, calleeCallableId, hostCall, intrinsicCall, null, null, null, null, span); } public Instruction( @@ -83,7 +184,7 @@ public record IRBackendExecutableFunction( final Integer expectedArgSlots, final Integer expectedRetSlots, final Span span) { - this(kind, calleeModuleKey, calleeCallableName, calleeCallableId, hostCall, intrinsicCall, null, null, expectedArgSlots, expectedRetSlots, span); + this(kind, ModuleId.none(), calleeModuleKey, calleeCallableName, calleeCallableId, hostCall, intrinsicCall, null, null, expectedArgSlots, expectedRetSlots, span); } public Instruction( @@ -93,7 +194,7 @@ public record IRBackendExecutableFunction( final HostCallMetadata hostCall, final IntrinsicCallMetadata intrinsicCall, final Span span) { - this(kind, calleeModuleKey, calleeCallableName, null, hostCall, intrinsicCall, null, null, null, null, span); + this(kind, ModuleId.none(), calleeModuleKey, calleeCallableName, null, hostCall, intrinsicCall, null, null, null, null, span); } public Instruction( @@ -105,12 +206,16 @@ public record IRBackendExecutableFunction( final Integer expectedArgSlots, final Integer expectedRetSlots, final Span span) { - this(kind, calleeModuleKey, calleeCallableName, null, hostCall, intrinsicCall, null, null, expectedArgSlots, expectedRetSlots, span); + this(kind, ModuleId.none(), calleeModuleKey, calleeCallableName, null, hostCall, intrinsicCall, null, null, expectedArgSlots, expectedRetSlots, span); } public Instruction { Objects.requireNonNull(kind, "kind"); span = span == null ? Span.none() : span; + calleeModuleId = calleeModuleId == null ? ModuleId.none() : calleeModuleId; + if (!calleeModuleId.isNone()) { + calleeModuleId.getIndex(); + } calleeModuleKey = calleeModuleKey == null ? "" : calleeModuleKey; calleeCallableName = calleeCallableName == null ? "" : calleeCallableName; label = label == null ? "" : label; diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendFile.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendFile.java index 86ea8415..f32d72a9 100644 --- a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendFile.java +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendFile.java @@ -3,6 +3,7 @@ package p.studio.compiler.models; import p.studio.compiler.source.identifiers.FileId; import p.studio.compiler.source.tables.CallableSignatureRef; import p.studio.compiler.source.tables.IntrinsicReference; +import p.studio.compiler.source.tables.ModuleReference; import p.studio.utilities.structures.ReadOnlyList; import java.util.Objects; @@ -12,6 +13,7 @@ public record IRBackendFile( ReadOnlyList functions, ReadOnlyList executableFunctions, IRReservedMetadata reservedMetadata, + ReadOnlyList modulePool, ReadOnlyList callableSignatures, ReadOnlyList intrinsicPool) { public IRBackendFile { @@ -19,6 +21,7 @@ public record IRBackendFile( functions = functions == null ? ReadOnlyList.empty() : functions; executableFunctions = executableFunctions == null ? ReadOnlyList.empty() : executableFunctions; reservedMetadata = reservedMetadata == null ? IRReservedMetadata.empty() : reservedMetadata; + modulePool = modulePool == null ? ReadOnlyList.empty() : modulePool; callableSignatures = callableSignatures == null ? ReadOnlyList.empty() : callableSignatures; intrinsicPool = intrinsicPool == null ? ReadOnlyList.empty() : intrinsicPool; } @@ -26,17 +29,55 @@ public record IRBackendFile( public IRBackendFile( final FileId fileId, final ReadOnlyList functions) { - this(fileId, functions, ReadOnlyList.empty(), IRReservedMetadata.empty(), ReadOnlyList.empty(), ReadOnlyList.empty()); + this( + fileId, + functions, + ReadOnlyList.empty(), + IRReservedMetadata.empty(), + ReadOnlyList.empty(), + ReadOnlyList.empty(), + ReadOnlyList.empty()); } public static IRBackendFile empty(final FileId fileId) { - return new IRBackendFile(fileId, ReadOnlyList.empty(), ReadOnlyList.empty(), IRReservedMetadata.empty(), ReadOnlyList.empty(), ReadOnlyList.empty()); + return new IRBackendFile( + fileId, + ReadOnlyList.empty(), + ReadOnlyList.empty(), + IRReservedMetadata.empty(), + ReadOnlyList.empty(), + ReadOnlyList.empty(), + ReadOnlyList.empty()); } public IRBackendFile( final FileId fileId, final ReadOnlyList functions, final IRReservedMetadata reservedMetadata) { - this(fileId, functions, ReadOnlyList.empty(), reservedMetadata, ReadOnlyList.empty(), ReadOnlyList.empty()); + this( + fileId, + functions, + ReadOnlyList.empty(), + reservedMetadata, + ReadOnlyList.empty(), + ReadOnlyList.empty(), + ReadOnlyList.empty()); + } + + public IRBackendFile( + final FileId fileId, + final ReadOnlyList functions, + final ReadOnlyList executableFunctions, + final IRReservedMetadata reservedMetadata, + final ReadOnlyList callableSignatures, + final ReadOnlyList intrinsicPool) { + this( + fileId, + functions, + executableFunctions, + reservedMetadata, + ReadOnlyList.empty(), + callableSignatures, + intrinsicPool); } } diff --git a/prometeu-compiler/prometeu-frontend-api/src/test/java/p/studio/compiler/models/IRBackendExecutableContractTest.java b/prometeu-compiler/prometeu-frontend-api/src/test/java/p/studio/compiler/models/IRBackendExecutableContractTest.java index c2f56c4b..3516a81a 100644 --- a/prometeu-compiler/prometeu-frontend-api/src/test/java/p/studio/compiler/models/IRBackendExecutableContractTest.java +++ b/prometeu-compiler/prometeu-frontend-api/src/test/java/p/studio/compiler/models/IRBackendExecutableContractTest.java @@ -5,8 +5,10 @@ import p.studio.compiler.source.Span; import p.studio.compiler.source.identifiers.CallableId; import p.studio.compiler.source.identifiers.FileId; import p.studio.compiler.source.identifiers.IntrinsicId; +import p.studio.compiler.source.identifiers.ModuleId; import p.studio.compiler.source.tables.CallableSignatureRef; import p.studio.compiler.source.tables.IntrinsicReference; +import p.studio.compiler.source.tables.ModuleReference; import p.studio.utilities.structures.ReadOnlyList; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -300,4 +302,76 @@ class IRBackendExecutableContractTest { assertEquals(21, emittedInstructionSpan.getStart()); assertEquals(34, emittedInstructionSpan.getEnd()); } + + @Test + void aggregatorMustReindexModulesIntoGlobalDensePool() { + final var fileA = new IRBackendFile( + new FileId(1), + ReadOnlyList.empty(), + ReadOnlyList.from(new IRBackendExecutableFunction( + new FileId(1), + new ModuleId(0), + "app:mod/a", + "entry", + new CallableId(0), + 0, + 5, + 0, + 0, + 0, + 1, + ReadOnlyList.from(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.RET, + "", + "", + null, + null, + Span.none())), + Span.none())), + IRReservedMetadata.empty(), + ReadOnlyList.from(new ModuleReference("app", ReadOnlyList.from("mod", "a"))), + ReadOnlyList.from(new CallableSignatureRef(new ModuleId(0), "app:mod/a", "entry", 0, "() -> unit")), + ReadOnlyList.empty()); + final var fileB = new IRBackendFile( + new FileId(2), + ReadOnlyList.empty(), + ReadOnlyList.from(new IRBackendExecutableFunction( + new FileId(2), + new ModuleId(0), + "app:mod/b", + "aux", + new CallableId(0), + 6, + 10, + 0, + 0, + 0, + 1, + ReadOnlyList.from(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.RET, + "", + "", + null, + null, + Span.none())), + Span.none())), + IRReservedMetadata.empty(), + ReadOnlyList.from(new ModuleReference("app", ReadOnlyList.from("mod", "b"))), + ReadOnlyList.from(new CallableSignatureRef(new ModuleId(0), "app:mod/b", "aux", 0, "() -> unit")), + ReadOnlyList.empty()); + + final var aggregator = IRBackend.aggregator(); + aggregator.merge(fileA); + aggregator.merge(fileB); + final var backend = aggregator.emit(); + + assertEquals(2, backend.getModulePool().size()); + assertEquals("mod", backend.getModulePool().get(0).pathSegments().get(0)); + assertEquals("a", backend.getModulePool().get(0).pathSegments().get(1)); + assertEquals("b", backend.getModulePool().get(1).pathSegments().get(1)); + assertEquals(0, backend.getExecutableFunctions().get(0).moduleId().getIndex()); + assertEquals(1, backend.getExecutableFunctions().get(1).moduleId().getIndex()); + assertEquals(0, backend.getCallableSignatures().get(0).moduleId().getIndex()); + assertEquals(1, backend.getCallableSignatures().get(1).moduleId().getIndex()); + } }