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 b3069cdf..a1940c09 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 @@ -16,6 +16,8 @@ import p.studio.compiler.pbs.semantics.PbsSemanticsErrors; import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.compiler.source.diagnostics.DiagnosticPhase; import p.studio.compiler.source.identifiers.FileId; +import p.studio.compiler.source.tables.CallableSignatureRef; +import p.studio.compiler.source.tables.CallableTable; import p.studio.compiler.source.tables.IntrinsicTable; import p.studio.utilities.structures.ReadOnlyList; @@ -121,13 +123,18 @@ public final class PbsFrontendCompiler { ? ReadOnlyList.empty() : lowerFunctions(fileId, ast); final var loweringErrorBaseline = diagnostics.errorCount(); - final ReadOnlyList executableFunctions = sourceKind == SourceKind.SDK_INTERFACE - ? ReadOnlyList.empty() + final var executableLowering = sourceKind == SourceKind.SDK_INTERFACE + ? new ExecutableLoweringResult(ReadOnlyList.empty(), ReadOnlyList.empty()) : lowerExecutableFunctions(fileId, ast, moduleKey, reservedMetadata, diagnostics); if (diagnostics.errorCount() > loweringErrorBaseline) { return IRBackendFile.empty(fileId); } - return new IRBackendFile(fileId, functions, executableFunctions, reservedMetadata); + return new IRBackendFile( + fileId, + functions, + executableLowering.executableFunctions(), + reservedMetadata, + executableLowering.callableSignatures()); } private ReadOnlyList lowerFunctions(final FileId fileId, final PbsAst.File ast) { @@ -143,12 +150,13 @@ public final class PbsFrontendCompiler { return ReadOnlyList.wrap(functions); } - private ReadOnlyList lowerExecutableFunctions( + private ExecutableLoweringResult lowerExecutableFunctions( final FileId fileId, final PbsAst.File ast, final String moduleKey, final IRReservedMetadata reservedMetadata, final DiagnosticSink diagnostics) { + final var normalizedModuleKey = moduleKey == null ? "" : moduleKey; final var hostByMethodName = new HashMap(); for (final var hostBinding : reservedMetadata.hostMethodBindings()) { hostByMethodName.put(hostBinding.sourceMethodName(), hostBinding); @@ -159,21 +167,32 @@ public final class PbsFrontendCompiler { intrinsicByMethodName.put(intrinsicSurface.sourceMethodName(), intrinsicSurface); } } - final var returnSlotsByCallableAndArity = new HashMap(); + final var callableIdTable = new CallableTable(); + final var callableIdsByNameAndArity = new HashMap>(); + final var callableIdByDeclaration = new HashMap(); + final var returnSlotsByCallableId = new HashMap(); final var intrinsicIdTable = new IntrinsicTable(); for (final var declaredFn : ast.functions()) { - final var key = callableArityKey(declaredFn.name(), declaredFn.parameters().size()); + final var callableId = callableIdTable.register( + normalizedModuleKey, + declaredFn.name(), + declaredFn.parameters().size(), + callableShapeKey(declaredFn)) + .getId(); + callableIdByDeclaration.put(declaredFn, callableId); + callableIdsByNameAndArity + .computeIfAbsent(callableArityKey(declaredFn.name(), declaredFn.parameters().size()), ignored -> new ArrayList<>()) + .add(callableId); final var retSlots = returnSlotsFor(declaredFn); - if (returnSlotsByCallableAndArity.containsKey(key) - && !returnSlotsByCallableAndArity.get(key).equals(retSlots)) { - returnSlotsByCallableAndArity.put(key, null); - } else { - returnSlotsByCallableAndArity.put(key, retSlots); - } + returnSlotsByCallableId.put(callableId, retSlots); } final var executableFunctions = new ArrayList(ast.functions().size()); for (final var fn : ast.functions()) { + final var functionCallableId = callableIdByDeclaration.get(fn); + if (functionCallableId == null) { + continue; + } final var instructions = new ArrayList(); final var callsites = new ArrayList(); collectCallsFromBlock(fn.body(), callsites); @@ -230,14 +249,37 @@ public final class PbsFrontendCompiler { continue; } + final var candidateCallableIds = callableIdsByNameAndArity.get(callableArityKey(calleeName, callExpr.arguments().size())); + if (candidateCallableIds == null || candidateCallableIds.isEmpty()) { + diagnostics.error( + DiagnosticPhase.STATIC_SEMANTICS, + PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(), + PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(), + Map.of(), + "executable lowering requires resolvable callee identity", + callExpr.span()); + continue; + } + if (candidateCallableIds.size() > 1) { + diagnostics.error( + DiagnosticPhase.STATIC_SEMANTICS, + PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(), + PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(), + Map.of(), + "executable lowering found ambiguous callable identity", + callExpr.span()); + continue; + } + final var calleeCallableId = candidateCallableIds.getFirst(); instructions.add(new IRBackendExecutableFunction.Instruction( IRBackendExecutableFunction.InstructionKind.CALL_FUNC, - moduleKey == null ? "" : moduleKey, + normalizedModuleKey, calleeName, + calleeCallableId, null, null, callExpr.arguments().size(), - returnSlotsByCallableAndArity.get(callableArityKey(calleeName, callExpr.arguments().size())), + returnSlotsByCallableId.get(calleeCallableId), callExpr.span())); } instructions.add(new IRBackendExecutableFunction.Instruction( @@ -246,6 +288,7 @@ public final class PbsFrontendCompiler { "", null, null, + null, fn.span())); final var returnSlots = returnSlotsFor(fn); @@ -253,8 +296,9 @@ public final class PbsFrontendCompiler { final var end = safeToInt(fn.span().getEnd()); executableFunctions.add(new IRBackendExecutableFunction( fileId, - moduleKey == null ? "" : moduleKey, + normalizedModuleKey, fn.name(), + functionCallableId, start, end, fn.parameters().size(), @@ -264,7 +308,13 @@ public final class PbsFrontendCompiler { ReadOnlyList.wrap(instructions), fn.span())); } - return ReadOnlyList.wrap(executableFunctions); + final var callableSignatures = new ArrayList(callableIdTable.size()); + for (final var callableId : callableIdTable.identifiers()) { + callableSignatures.add(callableIdTable.get(callableId)); + } + return new ExecutableLoweringResult( + ReadOnlyList.wrap(executableFunctions), + ReadOnlyList.wrap(callableSignatures)); } private int returnSlotsFor(final PbsAst.FunctionDecl functionDecl) { @@ -280,6 +330,60 @@ public final class PbsFrontendCompiler { return callableName + "#" + arity; } + private String callableShapeKey(final PbsAst.FunctionDecl functionDecl) { + final var builder = new StringBuilder(); + builder.append('('); + for (var i = 0; i < functionDecl.parameters().size(); i++) { + if (i > 0) { + builder.append(','); + } + builder.append(typeKey(functionDecl.parameters().get(i).typeRef())); + } + builder.append(")->"); + builder.append(outputShapeKey(functionDecl.returnKind(), functionDecl.returnType(), functionDecl.resultErrorType())); + return builder.toString(); + } + + private String outputShapeKey( + final PbsAst.ReturnKind returnKind, + final PbsAst.TypeRef returnType, + final PbsAst.TypeRef resultErrorType) { + return switch (returnKind) { + case INFERRED_UNIT, EXPLICIT_UNIT -> "unit"; + case PLAIN -> typeKey(returnType); + case RESULT -> "result<" + typeKey(resultErrorType) + ">" + typeKey(returnType); + }; + } + + private String typeKey(final PbsAst.TypeRef typeRef) { + final var unwrapped = unwrapGroup(typeRef); + if (unwrapped == null) { + return "unit"; + } + return switch (unwrapped.kind()) { + case SIMPLE -> "simple:" + unwrapped.name(); + case SELF -> "self"; + case OPTIONAL -> "optional(" + typeKey(unwrapped.inner()) + ")"; + case UNIT -> "unit"; + case GROUP -> typeKey(unwrapped.inner()); + case NAMED_TUPLE -> "tuple(" + unwrapped.fields().stream() + .map(field -> typeKey(field.typeRef())) + .reduce((left, right) -> left + "," + right) + .orElse("") + ")"; + case ERROR -> "error"; + }; + } + + private PbsAst.TypeRef unwrapGroup(final PbsAst.TypeRef typeRef) { + if (typeRef == null) { + return null; + } + if (typeRef.kind() != PbsAst.TypeRefKind.GROUP) { + return typeRef; + } + return unwrapGroup(typeRef.inner()); + } + private int safeToInt(final long value) { if (value > Integer.MAX_VALUE) { return Integer.MAX_VALUE; @@ -443,4 +547,9 @@ public final class PbsFrontendCompiler { default -> null; }; } + + private record ExecutableLoweringResult( + ReadOnlyList executableFunctions, + ReadOnlyList callableSignatures) { + } } 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 dff1abbc..59a488ec 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 @@ -15,6 +15,7 @@ public class LowerToIRVMService { private static final Comparator FUNCTION_ORDER = Comparator .comparing(IRBackendExecutableFunction::moduleKey) .thenComparing(IRBackendExecutableFunction::callableName) + .thenComparingInt(IRBackendExecutableFunction::callableId) .thenComparingInt(IRBackendExecutableFunction::sourceStart); private final IRVMValidator validator; @@ -35,11 +36,14 @@ public class LowerToIRVMService { } final var ordered = orderFunctions(backend.getExecutableFunctions()); - final var funcIdByModuleAndName = new HashMap(); + final var funcIdByCallableId = new HashMap(); for (var i = 0; i < ordered.size(); i++) { final var fn = ordered.get(i); - final var key = callableKey(fn.moduleKey(), fn.callableName()); - funcIdByModuleAndName.putIfAbsent(key, i); + if (funcIdByCallableId.putIfAbsent(fn.callableId(), i) != null) { + throw new IRVMLoweringException( + IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE, + "duplicate callable id in executable set: " + fn.callableId()); + } } final var loweredFunctions = new ArrayList(ordered.size()); @@ -66,12 +70,16 @@ public class LowerToIRVMService { functionPc += IRVMOp.RET.immediateSize() + 2; } case CALL_FUNC -> { - final var key = callableKey(instr.calleeModuleKey(), instr.calleeCallableName()); - final var calleeId = funcIdByModuleAndName.get(key); + if (instr.calleeCallableId() == null) { + throw new IRVMLoweringException( + IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE, + "missing callee callable id"); + } + final var calleeId = funcIdByCallableId.get(instr.calleeCallableId()); if (calleeId == null) { throw new IRVMLoweringException( IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE, - "missing callee function: " + key); + "missing callee function for callable_id=" + instr.calleeCallableId()); } final var calleeFunction = ordered.get(calleeId); if (instr.expectedArgSlots() != null @@ -79,7 +87,7 @@ public class LowerToIRVMService { throw new IRVMLoweringException( IRVMLoweringErrorCode.LOWER_IRVM_CALL_ARG_SLOTS_MISMATCH, "call arg_slots mismatch for %s. expected=%d actual=%d".formatted( - key, + instr.calleeCallableName(), instr.expectedArgSlots(), calleeFunction.paramSlots())); } @@ -88,7 +96,7 @@ public class LowerToIRVMService { throw new IRVMLoweringException( IRVMLoweringErrorCode.LOWER_IRVM_CALL_RET_SLOTS_MISMATCH, "call ret_slots mismatch for %s. expected=%d actual=%d".formatted( - key, + instr.calleeCallableName(), instr.expectedRetSlots(), calleeFunction.returnSlots())); } @@ -213,12 +221,6 @@ public class LowerToIRVMService { return ReadOnlyList.wrap(ordered); } - private String callableKey( - final String moduleKey, - final String callableName) { - return (moduleKey == null ? "" : moduleKey) + "::" + callableName; - } - 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 d76a5c28..f3c15282 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 @@ -17,10 +17,10 @@ class LowerToIRVMServiceTest { void lowerMustAssignEntrypointIdZeroAndSortRemainingDeterministically() { final var backend = IRBackend.builder() .executableFunctions(ReadOnlyList.from( - fn("aux", "app", ReadOnlyList.from( - callFunc("app", "main"), + fn("aux", "app", 11, ReadOnlyList.from( + callFunc("app", "main", 10), ret())), - fn("main", "app", ReadOnlyList.from( + fn("main", "app", 10, ReadOnlyList.from( ret())))) .build(); @@ -37,7 +37,7 @@ class LowerToIRVMServiceTest { void lowerMustMapHostAndIntrinsicCallsites() { final var backend = IRBackend.builder() .executableFunctions(ReadOnlyList.from( - fn("main", "app", ReadOnlyList.from( + fn("main", "app", 10, ReadOnlyList.from( callHost("gfx", "draw_pixel", 1, 2, 0), callIntrinsic("core.color.pack", 1, 0x2001), ret())))) @@ -59,8 +59,8 @@ class LowerToIRVMServiceTest { void lowerMustRejectUnterminatedFunction() { final var backend = IRBackend.builder() .executableFunctions(ReadOnlyList.from( - fn("main", "app", ReadOnlyList.from( - callFunc("app", "main"))))) + fn("main", "app", 10, ReadOnlyList.from( + callFunc("app", "main", 10))))) .build(); final var thrown = assertThrows(IRVMValidationException.class, () -> new LowerToIRVMService().lower(backend)); @@ -71,8 +71,8 @@ class LowerToIRVMServiceTest { void lowerMustRejectMissingCallee() { final var backend = IRBackend.builder() .executableFunctions(ReadOnlyList.from( - fn("main", "app", ReadOnlyList.from( - callFunc("app", "missing"), + fn("main", "app", 10, ReadOnlyList.from( + callFunc("app", "missing", 77), ret())))) .build(); @@ -84,9 +84,9 @@ class LowerToIRVMServiceTest { void lowerMustRejectCallArgSlotMismatch() { final var backend = IRBackend.builder() .executableFunctions(ReadOnlyList.from( - fn("callee", "app", ReadOnlyList.from(ret())), - fn("main", "app", ReadOnlyList.from( - callFuncWithExpected("app", "callee", 2, 0), + fn("callee", "app", 20, ReadOnlyList.from(ret())), + fn("main", "app", 10, ReadOnlyList.from( + callFuncWithExpected("app", "callee", 20, 2, 0), ret())))) .build(); @@ -98,7 +98,7 @@ class LowerToIRVMServiceTest { void lowerMustResolveJumpTargetsFromLabels() { final var backend = IRBackend.builder() .executableFunctions(ReadOnlyList.from( - fn("main", "app", ReadOnlyList.from( + fn("main", "app", 10, ReadOnlyList.from( label("entry"), jmp("exit"), label("exit"), @@ -117,7 +117,7 @@ class LowerToIRVMServiceTest { void lowerMustRejectMissingJumpTargetLabel() { final var backend = IRBackend.builder() .executableFunctions(ReadOnlyList.from( - fn("main", "app", ReadOnlyList.from( + fn("main", "app", 10, ReadOnlyList.from( jmp("missing"), ret())))) .build(); @@ -129,11 +129,13 @@ class LowerToIRVMServiceTest { private static IRBackendExecutableFunction fn( final String name, final String moduleKey, + final int callableId, final ReadOnlyList instructions) { return new IRBackendExecutableFunction( new FileId(0), moduleKey, name, + callableId, 0, 10, 0, @@ -154,11 +156,15 @@ class LowerToIRVMServiceTest { Span.none()); } - private static IRBackendExecutableFunction.Instruction callFunc(final String moduleKey, final String name) { + private static IRBackendExecutableFunction.Instruction callFunc( + final String moduleKey, + final String name, + final int calleeCallableId) { return new IRBackendExecutableFunction.Instruction( IRBackendExecutableFunction.InstructionKind.CALL_FUNC, moduleKey, name, + calleeCallableId, null, null, Span.none()); @@ -167,12 +173,14 @@ class LowerToIRVMServiceTest { private static IRBackendExecutableFunction.Instruction callFuncWithExpected( final String moduleKey, final String name, + final int calleeCallableId, final int expectedArgSlots, final int expectedRetSlots) { return new IRBackendExecutableFunction.Instruction( IRBackendExecutableFunction.InstructionKind.CALL_FUNC, moduleKey, name, + calleeCallableId, null, null, expectedArgSlots, @@ -215,6 +223,7 @@ class LowerToIRVMServiceTest { "", null, null, + null, label, "", null, @@ -229,6 +238,7 @@ class LowerToIRVMServiceTest { "", null, null, + null, "", target, null, diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/integration/BackendGateIIntegrationTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/integration/BackendGateIIntegrationTest.java index ece6ab61..727ff710 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/integration/BackendGateIIntegrationTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/integration/BackendGateIIntegrationTest.java @@ -178,6 +178,7 @@ class BackendGateIIntegrationTest { new FileId(1), "app/main", name, + 1, 0, 10, 0, diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/workspaces/stages/BackendSafetyGateSUTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/workspaces/stages/BackendSafetyGateSUTest.java index 95be4d3a..59917154 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/workspaces/stages/BackendSafetyGateSUTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/workspaces/stages/BackendSafetyGateSUTest.java @@ -29,6 +29,7 @@ class BackendSafetyGateSUTest { new FileId(1), "app", "main", + 1, 0, 10, 0, @@ -40,6 +41,7 @@ class BackendSafetyGateSUTest { IRBackendExecutableFunction.InstructionKind.CALL_FUNC, "app", "missing", + 99, null, null, 0, @@ -112,6 +114,7 @@ class BackendSafetyGateSUTest { new FileId(1), "app", "main", + 1, 0, 10, 0, diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/workspaces/stages/LowerToIRVMPipelineStageTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/workspaces/stages/LowerToIRVMPipelineStageTest.java index 4a0ae461..e277a46d 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/workspaces/stages/LowerToIRVMPipelineStageTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/workspaces/stages/LowerToIRVMPipelineStageTest.java @@ -38,6 +38,7 @@ class LowerToIRVMPipelineStageTest { new FileId(0), "app", "main", + 1, 0, 10, 0, diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/CallableId.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/CallableId.java new file mode 100644 index 00000000..3847f6a8 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/identifiers/CallableId.java @@ -0,0 +1,13 @@ +package p.studio.compiler.source.identifiers; + +public class CallableId extends SourceIdentifier { + public static final CallableId NONE = new CallableId(-1); + + public CallableId(final int id) { + super(id); + } + + public static CallableId none() { + return 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 new file mode 100644 index 00000000..95932d34 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableSignatureRef.java @@ -0,0 +1,22 @@ +package p.studio.compiler.source.tables; + +import java.util.Objects; + +public record CallableSignatureRef( + String moduleKey, + String callableName, + int arity, + String typeShape) { + + public CallableSignatureRef { + moduleKey = moduleKey == null ? "" : moduleKey; + callableName = Objects.requireNonNull(callableName, "callableName"); + typeShape = typeShape == null ? "" : typeShape; + if (callableName.isBlank()) { + throw new IllegalArgumentException("callableName must not be blank"); + } + if (arity < 0) { + throw new IllegalArgumentException("arity must be non-negative"); + } + } +} 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 new file mode 100644 index 00000000..659d6f74 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableTable.java @@ -0,0 +1,18 @@ +package p.studio.compiler.source.tables; + +import p.studio.compiler.source.identifiers.CallableId; + +public class CallableTable extends InternTable implements CallableTableReader { + + public CallableTable() { + super(CallableId::new); + } + + public CallableId register( + final String moduleKey, + final String callableName, + final int arity, + final String typeShape) { + return register(new CallableSignatureRef(moduleKey, callableName, arity, typeShape)); + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableTableReader.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableTableReader.java new file mode 100644 index 00000000..0e3fd2ec --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/source/tables/CallableTableReader.java @@ -0,0 +1,6 @@ +package p.studio.compiler.source.tables; + +import p.studio.compiler.source.identifiers.CallableId; + +public interface CallableTableReader extends InternTableReader { +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/tables/CallableTableTest.java b/prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/tables/CallableTableTest.java new file mode 100644 index 00000000..6d2e56f7 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/test/java/p/studio/compiler/source/tables/CallableTableTest.java @@ -0,0 +1,28 @@ +package p.studio.compiler.source.tables; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class CallableTableTest { + + @Test + void shouldInternEqualCallableSignaturesToSameIdentifier() { + final var table = new CallableTable(); + + final var first = table.register("app/main", "draw", 2, "(simple:int,simple:int)->unit"); + final var second = table.register("app/main", "draw", 2, "(simple:int,simple:int)->unit"); + final var third = table.register("app/main", "draw", 1, "(simple:int)->unit"); + + assertEquals(first, second); + assertEquals(2, table.size()); + assertEquals(1, table.get(third).arity()); + } + + @Test + void shouldRejectInvalidCallableSignatureReferenceContract() { + assertThrows(IllegalArgumentException.class, () -> new CallableSignatureRef("app/main", "", 1, "(unit)->unit")); + assertThrows(IllegalArgumentException.class, () -> new CallableSignatureRef("app/main", "draw", -1, "(unit)->unit")); + } +} 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 5c5c2dad..ce64d617 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 @@ -2,6 +2,8 @@ package p.studio.compiler.models; import lombok.Builder; import lombok.Getter; +import p.studio.compiler.source.tables.CallableSignatureRef; +import p.studio.compiler.source.tables.CallableTable; import p.studio.utilities.structures.ReadOnlyList; import java.util.ArrayList; @@ -15,6 +17,8 @@ public class IRBackend { @Builder.Default private final ReadOnlyList executableFunctions = ReadOnlyList.empty(); @Builder.Default + private final ReadOnlyList callableSignatures = ReadOnlyList.empty(); + @Builder.Default private final IRReservedMetadata reservedMetadata = IRReservedMetadata.empty(); public static IRBackendAggregator aggregator() { @@ -24,6 +28,7 @@ public class IRBackend { public static final class IRBackendAggregator { private final ArrayList functions = new ArrayList<>(); private final ArrayList executableFunctions = new ArrayList<>(); + private final CallableTable callableTable = new CallableTable(); private final ArrayList hostMethodBindings = new ArrayList<>(); private final ArrayList builtinTypeSurfaces = new ArrayList<>(); private final ArrayList builtinConstSurfaces = new ArrayList<>(); @@ -34,7 +39,10 @@ public class IRBackend { return; } functions.addAll(backendFile.functions().asList()); - executableFunctions.addAll(backendFile.executableFunctions().asList()); + final var callableRemap = reindexCallables(backendFile.callableSignatures()); + for (final var function : backendFile.executableFunctions()) { + executableFunctions.add(remapExecutableFunction(function, callableRemap)); + } final var metadata = backendFile.reservedMetadata(); hostMethodBindings.addAll(metadata.hostMethodBindings().asList()); builtinTypeSurfaces.addAll(metadata.builtinTypeSurfaces().asList()); @@ -42,11 +50,80 @@ public class IRBackend { requiredCapabilities.addAll(metadata.requiredCapabilities().asList()); } + private int[] reindexCallables(final ReadOnlyList localCallableSignatures) { + if (localCallableSignatures == null || localCallableSignatures.isEmpty()) { + return new int[0]; + } + final var remap = new int[localCallableSignatures.size()]; + for (var i = 0; i < localCallableSignatures.size(); i++) { + remap[i] = callableTable.register(localCallableSignatures.get(i)).getId(); + } + return remap; + } + + private IRBackendExecutableFunction remapExecutableFunction( + final IRBackendExecutableFunction function, + final int[] callableRemap) { + final var remappedInstructions = new ArrayList(function.instructions().size()); + for (final var instruction : function.instructions()) { + final Integer remappedCallee; + if (instruction.calleeCallableId() == null) { + remappedCallee = null; + } else { + remappedCallee = remapCallableId(instruction.calleeCallableId(), callableRemap, "callee"); + } + remappedInstructions.add(new IRBackendExecutableFunction.Instruction( + instruction.kind(), + instruction.calleeModuleKey(), + instruction.calleeCallableName(), + remappedCallee, + instruction.hostCall(), + instruction.intrinsicCall(), + instruction.label(), + instruction.targetLabel(), + instruction.expectedArgSlots(), + instruction.expectedRetSlots(), + instruction.span())); + } + return new IRBackendExecutableFunction( + function.fileId(), + function.moduleKey(), + function.callableName(), + remapCallableId(function.callableId(), callableRemap, "function"), + function.sourceStart(), + function.sourceEnd(), + function.paramSlots(), + function.localSlots(), + function.returnSlots(), + function.maxStackSlots(), + ReadOnlyList.wrap(remappedInstructions), + function.span()); + } + + private int remapCallableId( + final int localCallableId, + final int[] callableRemap, + final String label) { + if (localCallableId < 0 || localCallableId >= callableRemap.length) { + throw new IllegalArgumentException("invalid local " + label + " callable id: " + localCallableId); + } + return callableRemap[localCallableId]; + } + + private ReadOnlyList emitCallableSignatures() { + final var signatures = new ArrayList(callableTable.size()); + for (final var callableId : callableTable.identifiers()) { + signatures.add(callableTable.get(callableId)); + } + return ReadOnlyList.wrap(signatures); + } + public IRBackend emit() { return IRBackend .builder() .functions(ReadOnlyList.wrap(functions)) .executableFunctions(ReadOnlyList.wrap(executableFunctions)) + .callableSignatures(emitCallableSignatures()) .reservedMetadata(new IRReservedMetadata( ReadOnlyList.wrap(hostMethodBindings), ReadOnlyList.wrap(builtinTypeSurfaces), @@ -61,6 +138,7 @@ public class IRBackend { final var sb = new StringBuilder(); sb.append("IRBackend{functions=").append(functions.size()) .append(", executableFunctions=").append(executableFunctions.size()) + .append(", callableSignatures=").append(callableSignatures.size()) .append(", hostBindings=").append(reservedMetadata.hostMethodBindings().size()) .append(", builtinTypes=").append(reservedMetadata.builtinTypeSurfaces().size()) .append(", builtinConsts=").append(reservedMetadata.builtinConstSurfaces().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 69cfab36..278b809d 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 @@ -10,6 +10,7 @@ public record IRBackendExecutableFunction( FileId fileId, String moduleKey, String callableName, + int callableId, int sourceStart, int sourceEnd, int paramSlots, @@ -26,6 +27,9 @@ public record IRBackendExecutableFunction( if (callableName.isBlank()) { throw new IllegalArgumentException("callableName must not be blank"); } + if (callableId < 0) { + throw new IllegalArgumentException("callableId must be non-negative"); + } if (sourceStart < 0 || sourceEnd < 0 || sourceEnd < sourceStart) { throw new IllegalArgumentException("invalid source span bounds"); } @@ -46,6 +50,7 @@ public record IRBackendExecutableFunction( InstructionKind kind, String calleeModuleKey, String calleeCallableName, + Integer calleeCallableId, HostCallMetadata hostCall, IntrinsicCallMetadata intrinsicCall, String label, @@ -57,10 +62,34 @@ public record IRBackendExecutableFunction( final InstructionKind kind, final String calleeModuleKey, final String calleeCallableName, + final Integer calleeCallableId, final HostCallMetadata hostCall, final IntrinsicCallMetadata intrinsicCall, final Span span) { - this(kind, calleeModuleKey, calleeCallableName, hostCall, intrinsicCall, null, null, null, null, span); + this(kind, calleeModuleKey, calleeCallableName, calleeCallableId, hostCall, intrinsicCall, null, null, null, null, span); + } + + public Instruction( + final InstructionKind kind, + final String calleeModuleKey, + final String calleeCallableName, + final Integer calleeCallableId, + final HostCallMetadata hostCall, + final IntrinsicCallMetadata intrinsicCall, + final Integer expectedArgSlots, + final Integer expectedRetSlots, + final Span span) { + this(kind, calleeModuleKey, calleeCallableName, calleeCallableId, hostCall, intrinsicCall, null, null, expectedArgSlots, expectedRetSlots, span); + } + + public Instruction( + final InstructionKind kind, + final String calleeModuleKey, + final String calleeCallableName, + final HostCallMetadata hostCall, + final IntrinsicCallMetadata intrinsicCall, + final Span span) { + this(kind, calleeModuleKey, calleeCallableName, null, hostCall, intrinsicCall, null, null, null, null, span); } public Instruction( @@ -72,7 +101,7 @@ public record IRBackendExecutableFunction( final Integer expectedArgSlots, final Integer expectedRetSlots, final Span span) { - this(kind, calleeModuleKey, calleeCallableName, hostCall, intrinsicCall, null, null, expectedArgSlots, expectedRetSlots, span); + this(kind, calleeModuleKey, calleeCallableName, null, hostCall, intrinsicCall, null, null, expectedArgSlots, expectedRetSlots, span); } public Instruction { @@ -82,6 +111,9 @@ public record IRBackendExecutableFunction( calleeCallableName = calleeCallableName == null ? "" : calleeCallableName; label = label == null ? "" : label; targetLabel = targetLabel == null ? "" : targetLabel; + if (calleeCallableId != null && calleeCallableId < 0) { + throw new IllegalArgumentException("calleeCallableId must be non-negative"); + } if (expectedArgSlots != null && expectedArgSlots < 0) { throw new IllegalArgumentException("expectedArgSlots must be non-negative"); } @@ -90,8 +122,8 @@ public record IRBackendExecutableFunction( } switch (kind) { case CALL_FUNC -> { - if (calleeCallableName.isBlank()) { - throw new IllegalArgumentException("CALL_FUNC requires calleeCallableName"); + if (calleeCallableId == null) { + throw new IllegalArgumentException("CALL_FUNC requires calleeCallableId"); } if (hostCall != null || intrinsicCall != null) { throw new IllegalArgumentException("CALL_FUNC must not carry host or intrinsic metadata"); @@ -101,6 +133,9 @@ public record IRBackendExecutableFunction( if (hostCall == null) { throw new IllegalArgumentException("CALL_HOST requires hostCall metadata"); } + if (calleeCallableId != null) { + throw new IllegalArgumentException("CALL_HOST must not carry calleeCallableId"); + } if (intrinsicCall != null) { throw new IllegalArgumentException("CALL_HOST must not carry intrinsic metadata"); } @@ -109,12 +144,15 @@ public record IRBackendExecutableFunction( if (intrinsicCall == null) { throw new IllegalArgumentException("CALL_INTRINSIC requires intrinsic metadata"); } + if (calleeCallableId != null) { + throw new IllegalArgumentException("CALL_INTRINSIC must not carry calleeCallableId"); + } if (hostCall != null) { throw new IllegalArgumentException("CALL_INTRINSIC must not carry host metadata"); } } case HALT, RET -> { - if (!calleeCallableName.isBlank() || hostCall != null || intrinsicCall != null) { + if (!calleeCallableName.isBlank() || calleeCallableId != null || hostCall != null || intrinsicCall != null) { throw new IllegalArgumentException(kind + " must not carry callsite metadata"); } if (expectedArgSlots != null || expectedRetSlots != null) { @@ -127,6 +165,7 @@ public record IRBackendExecutableFunction( } if (!targetLabel.isBlank() || !calleeCallableName.isBlank() + || calleeCallableId != null || hostCall != null || intrinsicCall != null || expectedArgSlots != null @@ -140,6 +179,7 @@ public record IRBackendExecutableFunction( } if (!label.isBlank() || !calleeCallableName.isBlank() + || calleeCallableId != null || hostCall != null || intrinsicCall != null || expectedArgSlots != null 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 2c1da270..3daca9ed 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 @@ -1,6 +1,7 @@ package p.studio.compiler.models; import p.studio.compiler.source.identifiers.FileId; +import p.studio.compiler.source.tables.CallableSignatureRef; import p.studio.utilities.structures.ReadOnlyList; import java.util.Objects; @@ -9,28 +10,30 @@ public record IRBackendFile( FileId fileId, ReadOnlyList functions, ReadOnlyList executableFunctions, - IRReservedMetadata reservedMetadata) { + IRReservedMetadata reservedMetadata, + ReadOnlyList callableSignatures) { public IRBackendFile { fileId = Objects.requireNonNull(fileId, "fileId"); functions = functions == null ? ReadOnlyList.empty() : functions; executableFunctions = executableFunctions == null ? ReadOnlyList.empty() : executableFunctions; reservedMetadata = reservedMetadata == null ? IRReservedMetadata.empty() : reservedMetadata; + callableSignatures = callableSignatures == null ? ReadOnlyList.empty() : callableSignatures; } public IRBackendFile( final FileId fileId, final ReadOnlyList functions) { - this(fileId, functions, ReadOnlyList.empty(), IRReservedMetadata.empty()); + this(fileId, functions, ReadOnlyList.empty(), IRReservedMetadata.empty(), ReadOnlyList.empty()); } public static IRBackendFile empty(final FileId fileId) { - return new IRBackendFile(fileId, ReadOnlyList.empty(), ReadOnlyList.empty(), IRReservedMetadata.empty()); + return new IRBackendFile(fileId, ReadOnlyList.empty(), ReadOnlyList.empty(), IRReservedMetadata.empty(), ReadOnlyList.empty()); } public IRBackendFile( final FileId fileId, final ReadOnlyList functions, final IRReservedMetadata reservedMetadata) { - this(fileId, functions, ReadOnlyList.empty(), reservedMetadata); + this(fileId, functions, ReadOnlyList.empty(), reservedMetadata, ReadOnlyList.empty()); } } 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 743726a7..4052edb5 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 @@ -3,6 +3,7 @@ package p.studio.compiler.models; import org.junit.jupiter.api.Test; import p.studio.compiler.source.Span; import p.studio.compiler.source.identifiers.FileId; +import p.studio.compiler.source.tables.CallableSignatureRef; import p.studio.utilities.structures.ReadOnlyList; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -25,6 +26,7 @@ class IRBackendExecutableContractTest { IRBackendExecutableFunction.InstructionKind.CALL_FUNC, "app/main", "foo", + 7, null, null, Span.none()); @@ -37,6 +39,7 @@ class IRBackendExecutableContractTest { new FileId(1), "app", "main", + 1, 10, 5, 0, @@ -50,6 +53,7 @@ class IRBackendExecutableContractTest { new FileId(1), "app", "main", + 1, 0, 10, 0, @@ -67,6 +71,7 @@ class IRBackendExecutableContractTest { IRBackendExecutableFunction.InstructionKind.CALL_FUNC, "app/main", "foo", + 1, new IRBackendExecutableFunction.HostCallMetadata("gfx", "draw", 1, 0, 0), null, Span.none())); @@ -87,6 +92,7 @@ class IRBackendExecutableContractTest { "", null, null, + null, "", "", null, @@ -99,6 +105,7 @@ class IRBackendExecutableContractTest { "", null, null, + null, "", "", null, @@ -116,6 +123,7 @@ class IRBackendExecutableContractTest { "app/main", "entry", 0, + 0, 10, 0, 0, @@ -127,9 +135,11 @@ class IRBackendExecutableContractTest { "", null, null, + null, Span.none())), Span.none())), - IRReservedMetadata.empty()); + IRReservedMetadata.empty(), + ReadOnlyList.from(new CallableSignatureRef("app/main", "entry", 0, "() -> unit"))); final var fileB = new IRBackendFile( new FileId(2), ReadOnlyList.empty(), @@ -137,6 +147,7 @@ class IRBackendExecutableContractTest { new FileId(2), "app/main", "aux", + 0, 11, 20, 0, @@ -149,9 +160,11 @@ class IRBackendExecutableContractTest { "", null, null, + null, Span.none())), Span.none())), - IRReservedMetadata.empty()); + IRReservedMetadata.empty(), + ReadOnlyList.from(new CallableSignatureRef("app/main", "aux", 0, "() -> unit"))); final var aggregator = IRBackend.aggregator(); aggregator.merge(fileA); @@ -161,5 +174,6 @@ class IRBackendExecutableContractTest { assertEquals(2, backend.getExecutableFunctions().size()); assertEquals("entry", backend.getExecutableFunctions().get(0).callableName()); assertEquals("aux", backend.getExecutableFunctions().get(1).callableName()); + assertEquals(2, backend.getCallableSignatures().size()); } }