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