implements PR-O4.8

This commit is contained in:
bQUARKz 2026-03-07 19:53:03 +00:00
parent a65018f72c
commit 6288056c7a
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
6 changed files with 260 additions and 2 deletions

View File

@ -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);
}
}

View File

@ -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=-

View File

@ -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);
};
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1 @@
504253000000000004000000000000000000000000000000000000000000000001000000500000001400000002000000640000000e00000003000000720000004400000005000000b60000001b00000001000000000000000e000000000000000000020071000000000072000000000051000300000000000000010000000000000000000000060000000100000000000000000000000c0000000100000000000000000000000100000000000000040000006d61696e0100000003006766780a00647261775f706978656c010000000000

View File

@ -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=-