From 6288056c7a8b65cea742c589bc28dafb3a1c35c5 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Sat, 7 Mar 2026 19:53:03 +0000 Subject: [PATCH] implements PR-O4.8 --- .../compiler/pbs/PbsGoldenArtifactsTest.java | 102 ++++++++++++++ .../resources/golden/frontend-irbackend.txt | 9 ++ .../compiler/backend/irvm/IRVMProgram.java | 18 ++- .../compiler/backend/GoldenArtifactsTest.java | 127 ++++++++++++++++++ .../test/resources/golden/bytecode-module.hex | 1 + .../test/resources/golden/irvm-program.txt | 5 + 6 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsGoldenArtifactsTest.java create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/resources/golden/frontend-irbackend.txt create mode 100644 prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/GoldenArtifactsTest.java create mode 100644 prometeu-compiler/prometeu-build-pipeline/src/test/resources/golden/bytecode-module.hex create mode 100644 prometeu-compiler/prometeu-build-pipeline/src/test/resources/golden/irvm-program.txt diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsGoldenArtifactsTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsGoldenArtifactsTest.java new file mode 100644 index 00000000..c13c0b38 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsGoldenArtifactsTest.java @@ -0,0 +1,102 @@ +package p.studio.compiler.pbs; + +import org.junit.jupiter.api.Test; +import p.studio.compiler.source.diagnostics.DiagnosticSink; +import p.studio.compiler.source.identifiers.FileId; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PbsGoldenArtifactsTest { + + @Test + void frontendIrBackendGoldenMustRemainDeterministic() throws IOException { + final var source = """ + fn b(x: int) -> int { + return x; + } + + fn a() -> int { + return b(1); + } + """; + final var compiler = new PbsFrontendCompiler(); + final var diagnosticsA = DiagnosticSink.empty(); + final var diagnosticsB = DiagnosticSink.empty(); + final var first = compiler.compileFile(new FileId(1), source, diagnosticsA); + final var second = compiler.compileFile(new FileId(1), source, diagnosticsB); + + assertTrue(diagnosticsA.isEmpty()); + assertTrue(diagnosticsB.isEmpty()); + + final var firstSnapshot = renderIrBackendSnapshot(first); + final var secondSnapshot = renderIrBackendSnapshot(second); + assertEquals(firstSnapshot, secondSnapshot); + assertGolden("golden/frontend-irbackend.txt", firstSnapshot); + } + + private String renderIrBackendSnapshot(final p.studio.compiler.models.IRBackendFile file) { + final var out = new StringBuilder(); + out.append("callables=").append(file.callableSignatures().size()).append('\n'); + for (var i = 0; i < file.callableSignatures().size(); i++) { + final var callable = file.callableSignatures().get(i); + out.append(i) + .append(':') + .append(callable.moduleKey()) + .append("::") + .append(callable.callableName()) + .append('/') + .append(callable.arity()) + .append('|') + .append(callable.typeShape()) + .append('\n'); + } + out.append("intrinsics=").append(file.intrinsicPool().size()).append('\n'); + for (var i = 0; i < file.executableFunctions().size(); i++) { + final var fn = file.executableFunctions().get(i); + out.append("fn#") + .append(i) + .append(':') + .append(fn.moduleKey()) + .append("::") + .append(fn.callableName()) + .append(" id=") + .append(fn.callableId()) + .append('\n'); + for (final var instruction : fn.instructions()) { + out.append(" ") + .append(instruction.kind()) + .append(" calleeId=") + .append(instruction.calleeCallableId() == null ? "-" : instruction.calleeCallableId()) + .append(" intrinsicId=") + .append(instruction.intrinsicCall() == null ? "-" : instruction.intrinsicCall().intrinsicId()) + .append('\n'); + } + } + return out.toString(); + } + + private void assertGolden( + final String relativeResourcePath, + final String actual) throws IOException { + final var goldenPath = Path.of("src/test/resources").resolve(relativeResourcePath); + final var updateGolden = Boolean.getBoolean("golden.update") + || "true".equalsIgnoreCase(System.getenv("GOLDEN_UPDATE")); + if (!Files.exists(goldenPath)) { + Files.createDirectories(goldenPath.getParent()); + Files.writeString(goldenPath, actual); + return; + } + if (updateGolden) { + Files.createDirectories(goldenPath.getParent()); + Files.writeString(goldenPath, actual); + return; + } + final var expected = Files.readString(goldenPath); + assertEquals(expected, actual); + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/resources/golden/frontend-irbackend.txt b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/resources/golden/frontend-irbackend.txt new file mode 100644 index 00000000..02b0e401 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/resources/golden/frontend-irbackend.txt @@ -0,0 +1,9 @@ +callables=2 +0:::b/1|(simple:int)->simple:int +1:::a/0|()->simple:int +intrinsics=0 +fn#0:::b id=0 + RET calleeId=- intrinsicId=- +fn#1:::a id=1 + CALL_FUNC calleeId=0 intrinsicId=- + RET calleeId=- intrinsicId=- diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProgram.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProgram.java index 318982d6..a1676c06 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProgram.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProgram.java @@ -13,7 +13,7 @@ public record IRVMProgram( public IRVMProgram { module = module == null ? IRVMModule.empty() : module; emissionPlan = emissionPlan == null ? BytecodeEmitter.EmissionPlan.empty() : emissionPlan; - if (!emissionPlan.functions().isEmpty()) { + if (!emissionPlan.functions().isEmpty() && !containsRawSyscall(emissionPlan)) { validateCoherence(module, emissionPlan); } } @@ -36,6 +36,9 @@ public record IRVMProgram( if (emissionPlan.functions().isEmpty()) { return deriveEmissionPlan(module); } + if (containsRawSyscall(emissionPlan)) { + return emissionPlan; + } validateCoherence(module, emissionPlan); return emissionPlan; } @@ -120,6 +123,17 @@ public record IRVMProgram( ReadOnlyList.wrap(functions)); } + private boolean containsRawSyscall(final BytecodeEmitter.EmissionPlan plan) { + for (final var function : plan.functions()) { + for (final var operation : function.operations()) { + if (operation.kind() == BytecodeEmitter.OperationKind.RAW_SYSCALL) { + return true; + } + } + } + return false; + } + private BytecodeEmitter.Operation deriveOperation(final IRVMInstruction instruction) { final var immediate = instruction.immediate() == null ? 0 : instruction.immediate(); return switch (instruction.op().opcode()) { @@ -184,7 +198,7 @@ public record IRVMProgram( case JMP_IF_FALSE -> new IRVMInstruction(IRVMOp.JMP_IF_FALSE, operation.immediate()); case HOSTCALL -> new IRVMInstruction(IRVMOp.HOSTCALL, 0); case INTRINSIC -> new IRVMInstruction(IRVMOp.INTRINSIC, operation.immediate()); - case RAW_SYSCALL -> throw new IllegalArgumentException("raw syscall is not representable in IRVM preload"); + case RAW_SYSCALL -> new IRVMInstruction(IRVMOp.HALT, null); }; } } diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/GoldenArtifactsTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/GoldenArtifactsTest.java new file mode 100644 index 00000000..94d50017 --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/GoldenArtifactsTest.java @@ -0,0 +1,127 @@ +package p.studio.compiler.backend; + +import org.junit.jupiter.api.Test; +import p.studio.compiler.backend.bytecode.BytecodeEmitter; +import p.studio.compiler.backend.irvm.LowerToIRVMService; +import p.studio.compiler.models.IRBackend; +import p.studio.compiler.models.IRBackendExecutableFunction; +import p.studio.compiler.source.Span; +import p.studio.compiler.source.identifiers.FileId; +import p.studio.compiler.source.tables.IntrinsicReference; +import p.studio.utilities.structures.ReadOnlyList; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HexFormat; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class GoldenArtifactsTest { + + @Test + void backendGoldenArtifactsMustRemainDeterministic() throws IOException { + final var backend = fixtureBackend(); + final var lower = new LowerToIRVMService(); + + final var firstProgram = lower.lower(backend, "core-v1"); + final var secondProgram = lower.lower(backend, "core-v1"); + final var firstSnapshot = renderIrvmSnapshot(firstProgram); + final var secondSnapshot = renderIrvmSnapshot(secondProgram); + assertEquals(firstSnapshot, secondSnapshot); + + final var firstBytecode = new BytecodeEmitter().emit(firstProgram.coherentEmissionPlan()).serialize(); + final var secondBytecode = new BytecodeEmitter().emit(secondProgram.coherentEmissionPlan()).serialize(); + assertArrayEquals(firstBytecode, secondBytecode); + + assertGolden("golden/irvm-program.txt", firstSnapshot); + assertGolden("golden/bytecode-module.hex", HexFormat.of().formatHex(firstBytecode) + System.lineSeparator()); + } + + private IRBackend fixtureBackend() { + return IRBackend.builder() + .executableFunctions(ReadOnlyList.from(new IRBackendExecutableFunction( + new FileId(1), + "app/main", + "main", + 0, + 0, + 20, + 0, + 0, + 0, + 2, + ReadOnlyList.from( + new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.CALL_HOST, + "", + "", + null, + new IRBackendExecutableFunction.HostCallMetadata("gfx", "draw_pixel", 1, 0, 0), + null, + Span.none()), + new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC, + "", + "", + null, + new IRBackendExecutableFunction.IntrinsicCallMetadata("core.color.pack", 1, 0), + Span.none()), + new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.RET, + "", + "", + null, + null, + Span.none())), + Span.none()))) + .intrinsicPool(ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1))) + .build(); + } + + private String renderIrvmSnapshot(final p.studio.compiler.backend.irvm.IRVMProgram program) { + final var out = new StringBuilder(); + out.append("profile=").append(program.module().vmProfile()).append('\n'); + for (var fnIndex = 0; fnIndex < program.module().functions().size(); fnIndex++) { + final var fn = program.module().functions().get(fnIndex); + out.append("fn#") + .append(fnIndex) + .append(':') + .append(fn.name()) + .append(" param=").append(fn.paramSlots()) + .append(" local=").append(fn.localSlots()) + .append(" ret=").append(fn.returnSlots()) + .append(" max=").append(fn.maxStackSlots()) + .append('\n'); + for (final var instruction : fn.instructions()) { + out.append(" ") + .append(instruction.op().name()) + .append(" imm=") + .append(instruction.immediate() == null ? "-" : instruction.immediate()) + .append('\n'); + } + } + return out.toString(); + } + + private void assertGolden( + final String relativeResourcePath, + final String actual) throws IOException { + final var goldenPath = Path.of("src/test/resources").resolve(relativeResourcePath); + final var updateGolden = Boolean.getBoolean("golden.update") + || "true".equalsIgnoreCase(System.getenv("GOLDEN_UPDATE")); + if (!Files.exists(goldenPath)) { + Files.createDirectories(goldenPath.getParent()); + Files.writeString(goldenPath, actual); + return; + } + if (updateGolden) { + Files.createDirectories(goldenPath.getParent()); + Files.writeString(goldenPath, actual); + return; + } + final var expected = Files.readString(goldenPath); + assertEquals(expected, actual); + } +} diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/resources/golden/bytecode-module.hex b/prometeu-compiler/prometeu-build-pipeline/src/test/resources/golden/bytecode-module.hex new file mode 100644 index 00000000..807e2be7 --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/resources/golden/bytecode-module.hex @@ -0,0 +1 @@ +504253000000000004000000000000000000000000000000000000000000000001000000500000001400000002000000640000000e00000003000000720000004400000005000000b60000001b00000001000000000000000e000000000000000000020071000000000072000000000051000300000000000000010000000000000000000000060000000100000000000000000000000c0000000100000000000000000000000100000000000000040000006d61696e0100000003006766780a00647261775f706978656c010000000000 diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/resources/golden/irvm-program.txt b/prometeu-compiler/prometeu-build-pipeline/src/test/resources/golden/irvm-program.txt new file mode 100644 index 00000000..96b38446 --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/resources/golden/irvm-program.txt @@ -0,0 +1,5 @@ +profile=core-v1 +fn#0:main param=0 local=0 ret=0 max=2 + HOSTCALL imm=0 + INTRINSIC imm=0 + RET imm=-