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 c7c237b7..5c5c2dad 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 @@ -13,6 +13,8 @@ public class IRBackend { @Builder.Default private final ReadOnlyList functions = ReadOnlyList.empty(); @Builder.Default + private final ReadOnlyList executableFunctions = ReadOnlyList.empty(); + @Builder.Default private final IRReservedMetadata reservedMetadata = IRReservedMetadata.empty(); public static IRBackendAggregator aggregator() { @@ -21,6 +23,7 @@ public class IRBackend { public static final class IRBackendAggregator { private final ArrayList functions = new ArrayList<>(); + private final ArrayList executableFunctions = new ArrayList<>(); private final ArrayList hostMethodBindings = new ArrayList<>(); private final ArrayList builtinTypeSurfaces = new ArrayList<>(); private final ArrayList builtinConstSurfaces = new ArrayList<>(); @@ -31,6 +34,7 @@ public class IRBackend { return; } functions.addAll(backendFile.functions().asList()); + executableFunctions.addAll(backendFile.executableFunctions().asList()); final var metadata = backendFile.reservedMetadata(); hostMethodBindings.addAll(metadata.hostMethodBindings().asList()); builtinTypeSurfaces.addAll(metadata.builtinTypeSurfaces().asList()); @@ -42,6 +46,7 @@ public class IRBackend { return IRBackend .builder() .functions(ReadOnlyList.wrap(functions)) + .executableFunctions(ReadOnlyList.wrap(executableFunctions)) .reservedMetadata(new IRReservedMetadata( ReadOnlyList.wrap(hostMethodBindings), ReadOnlyList.wrap(builtinTypeSurfaces), @@ -55,6 +60,7 @@ public class IRBackend { public String toString() { final var sb = new StringBuilder(); sb.append("IRBackend{functions=").append(functions.size()) + .append(", executableFunctions=").append(executableFunctions.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 new file mode 100644 index 00000000..d09906e8 --- /dev/null +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendExecutableFunction.java @@ -0,0 +1,93 @@ +package p.studio.compiler.models; + +import p.studio.compiler.source.Span; +import p.studio.compiler.source.identifiers.FileId; +import p.studio.utilities.structures.ReadOnlyList; + +import java.util.Objects; + +public record IRBackendExecutableFunction( + FileId fileId, + String moduleKey, + String callableName, + int sourceStart, + int sourceEnd, + int paramSlots, + int localSlots, + int returnSlots, + int maxStackSlots, + ReadOnlyList instructions, + Span span) { + + public IRBackendExecutableFunction { + fileId = Objects.requireNonNull(fileId, "fileId"); + moduleKey = moduleKey == null ? "" : moduleKey; + callableName = Objects.requireNonNull(callableName, "callableName"); + instructions = instructions == null ? ReadOnlyList.empty() : instructions; + span = span == null ? Span.none() : span; + } + + public record Instruction( + InstructionKind kind, + String calleeModuleKey, + String calleeCallableName, + HostCallMetadata hostCall, + IntrinsicCallMetadata intrinsicCall, + Span span) { + public Instruction { + Objects.requireNonNull(kind, "kind"); + span = span == null ? Span.none() : span; + calleeModuleKey = calleeModuleKey == null ? "" : calleeModuleKey; + calleeCallableName = calleeCallableName == null ? "" : calleeCallableName; + switch (kind) { + case CALL_FUNC -> { + if (calleeCallableName.isBlank()) { + throw new IllegalArgumentException("CALL_FUNC requires calleeCallableName"); + } + } + case CALL_HOST -> { + if (hostCall == null) { + throw new IllegalArgumentException("CALL_HOST requires hostCall metadata"); + } + } + case CALL_INTRINSIC -> { + if (intrinsicCall == null) { + throw new IllegalArgumentException("CALL_INTRINSIC requires intrinsic metadata"); + } + } + case HALT, RET -> { + } + } + } + } + + public enum InstructionKind { + HALT, + RET, + CALL_FUNC, + CALL_HOST, + CALL_INTRINSIC, + } + + public record HostCallMetadata( + String module, + String name, + long version, + int argSlots, + int retSlots) { + public HostCallMetadata { + module = Objects.requireNonNull(module, "module"); + name = Objects.requireNonNull(name, "name"); + } + } + + public record IntrinsicCallMetadata( + String canonicalName, + long canonicalVersion, + int intrinsicId) { + public IntrinsicCallMetadata { + canonicalName = Objects.requireNonNull(canonicalName, "canonicalName"); + } + } +} + 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 6e99f20b..2c1da270 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 @@ -8,20 +8,29 @@ import java.util.Objects; public record IRBackendFile( FileId fileId, ReadOnlyList functions, + ReadOnlyList executableFunctions, IRReservedMetadata reservedMetadata) { 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; } public IRBackendFile( final FileId fileId, final ReadOnlyList functions) { - this(fileId, functions, IRReservedMetadata.empty()); + this(fileId, functions, ReadOnlyList.empty(), IRReservedMetadata.empty()); } public static IRBackendFile empty(final FileId fileId) { - return new IRBackendFile(fileId, ReadOnlyList.empty(), IRReservedMetadata.empty()); + return new IRBackendFile(fileId, ReadOnlyList.empty(), ReadOnlyList.empty(), IRReservedMetadata.empty()); + } + + public IRBackendFile( + final FileId fileId, + final ReadOnlyList functions, + final IRReservedMetadata reservedMetadata) { + this(fileId, functions, ReadOnlyList.empty(), reservedMetadata); } } 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 new file mode 100644 index 00000000..b64d1c23 --- /dev/null +++ b/prometeu-compiler/prometeu-frontend-api/src/test/java/p/studio/compiler/models/IRBackendExecutableContractTest.java @@ -0,0 +1,90 @@ +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.utilities.structures.ReadOnlyList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class IRBackendExecutableContractTest { + + @Test + void callInstructionMustRequireCategorySpecificMetadata() { + assertThrows(IllegalArgumentException.class, () -> new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.CALL_HOST, + "", + "", + null, + null, + Span.none())); + + final var callFunc = new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.CALL_FUNC, + "app/main", + "foo", + null, + null, + Span.none()); + assertEquals(IRBackendExecutableFunction.InstructionKind.CALL_FUNC, callFunc.kind()); + } + + @Test + void aggregatorMustPreserveExecutableFunctionOrderDeterministically() { + final var fileA = new IRBackendFile( + new FileId(1), + ReadOnlyList.empty(), + ReadOnlyList.from(new IRBackendExecutableFunction( + new FileId(1), + "app/main", + "entry", + 0, + 10, + 0, + 0, + 0, + 1, + ReadOnlyList.from(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.HALT, + "", + "", + null, + null, + Span.none())), + Span.none())), + IRReservedMetadata.empty()); + final var fileB = new IRBackendFile( + new FileId(2), + ReadOnlyList.empty(), + ReadOnlyList.from(new IRBackendExecutableFunction( + new FileId(2), + "app/main", + "aux", + 11, + 20, + 0, + 0, + 0, + 1, + ReadOnlyList.from(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.RET, + "", + "", + null, + null, + Span.none())), + Span.none())), + IRReservedMetadata.empty()); + + final var aggregator = IRBackend.aggregator(); + aggregator.merge(fileA); + aggregator.merge(fileB); + final var backend = aggregator.emit(); + + assertEquals(2, backend.getExecutableFunctions().size()); + assertEquals("entry", backend.getExecutableFunctions().get(0).callableName()); + assertEquals("aux", backend.getExecutableFunctions().get(1).callableName()); + } +} +