implements PR-O4.8
This commit is contained in:
parent
a65018f72c
commit
6288056c7a
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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=-
|
||||||
@ -13,7 +13,7 @@ public record IRVMProgram(
|
|||||||
public IRVMProgram {
|
public IRVMProgram {
|
||||||
module = module == null ? IRVMModule.empty() : module;
|
module = module == null ? IRVMModule.empty() : module;
|
||||||
emissionPlan = emissionPlan == null ? BytecodeEmitter.EmissionPlan.empty() : emissionPlan;
|
emissionPlan = emissionPlan == null ? BytecodeEmitter.EmissionPlan.empty() : emissionPlan;
|
||||||
if (!emissionPlan.functions().isEmpty()) {
|
if (!emissionPlan.functions().isEmpty() && !containsRawSyscall(emissionPlan)) {
|
||||||
validateCoherence(module, emissionPlan);
|
validateCoherence(module, emissionPlan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,6 +36,9 @@ public record IRVMProgram(
|
|||||||
if (emissionPlan.functions().isEmpty()) {
|
if (emissionPlan.functions().isEmpty()) {
|
||||||
return deriveEmissionPlan(module);
|
return deriveEmissionPlan(module);
|
||||||
}
|
}
|
||||||
|
if (containsRawSyscall(emissionPlan)) {
|
||||||
|
return emissionPlan;
|
||||||
|
}
|
||||||
validateCoherence(module, emissionPlan);
|
validateCoherence(module, emissionPlan);
|
||||||
return emissionPlan;
|
return emissionPlan;
|
||||||
}
|
}
|
||||||
@ -120,6 +123,17 @@ public record IRVMProgram(
|
|||||||
ReadOnlyList.wrap(functions));
|
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) {
|
private BytecodeEmitter.Operation deriveOperation(final IRVMInstruction instruction) {
|
||||||
final var immediate = instruction.immediate() == null ? 0 : instruction.immediate();
|
final var immediate = instruction.immediate() == null ? 0 : instruction.immediate();
|
||||||
return switch (instruction.op().opcode()) {
|
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 JMP_IF_FALSE -> new IRVMInstruction(IRVMOp.JMP_IF_FALSE, operation.immediate());
|
||||||
case HOSTCALL -> new IRVMInstruction(IRVMOp.HOSTCALL, 0);
|
case HOSTCALL -> new IRVMInstruction(IRVMOp.HOSTCALL, 0);
|
||||||
case INTRINSIC -> new IRVMInstruction(IRVMOp.INTRINSIC, operation.immediate());
|
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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
504253000000000004000000000000000000000000000000000000000000000001000000500000001400000002000000640000000e00000003000000720000004400000005000000b60000001b00000001000000000000000e000000000000000000020071000000000072000000000051000300000000000000010000000000000000000000060000000100000000000000000000000c0000000100000000000000000000000100000000000000040000006d61696e0100000003006766780a00647261775f706978656c010000000000
|
||||||
@ -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=-
|
||||||
Loading…
x
Reference in New Issue
Block a user