implements PR-O4.2

This commit is contained in:
bQUARKz 2026-03-07 19:36:19 +00:00
parent d4f6d96437
commit 3449771f33
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
8 changed files with 160 additions and 17 deletions

View File

@ -18,6 +18,7 @@ import p.studio.compiler.source.diagnostics.DiagnosticPhase;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.compiler.source.tables.CallableSignatureRef;
import p.studio.compiler.source.tables.CallableTable;
import p.studio.compiler.source.tables.IntrinsicReference;
import p.studio.compiler.source.tables.IntrinsicTable;
import p.studio.utilities.structures.ReadOnlyList;
@ -124,7 +125,7 @@ public final class PbsFrontendCompiler {
: lowerFunctions(fileId, ast);
final var loweringErrorBaseline = diagnostics.errorCount();
final var executableLowering = sourceKind == SourceKind.SDK_INTERFACE
? new ExecutableLoweringResult(ReadOnlyList.empty(), ReadOnlyList.empty())
? new ExecutableLoweringResult(ReadOnlyList.empty(), ReadOnlyList.empty(), ReadOnlyList.empty())
: lowerExecutableFunctions(fileId, ast, moduleKey, reservedMetadata, diagnostics);
if (diagnostics.errorCount() > loweringErrorBaseline) {
return IRBackendFile.empty(fileId);
@ -134,7 +135,8 @@ public final class PbsFrontendCompiler {
functions,
executableLowering.executableFunctions(),
reservedMetadata,
executableLowering.callableSignatures());
executableLowering.callableSignatures(),
executableLowering.intrinsicPool());
}
private ReadOnlyList<IRFunction> lowerFunctions(final FileId fileId, final PbsAst.File ast) {
@ -312,9 +314,14 @@ public final class PbsFrontendCompiler {
for (final var callableId : callableIdTable.identifiers()) {
callableSignatures.add(callableIdTable.get(callableId));
}
final var intrinsicPool = new ArrayList<IntrinsicReference>(intrinsicIdTable.size());
for (final var intrinsicId : intrinsicIdTable.identifiers()) {
intrinsicPool.add(intrinsicIdTable.get(intrinsicId));
}
return new ExecutableLoweringResult(
ReadOnlyList.wrap(executableFunctions),
ReadOnlyList.wrap(callableSignatures));
ReadOnlyList.wrap(callableSignatures),
ReadOnlyList.wrap(intrinsicPool));
}
private int returnSlotsFor(final PbsAst.FunctionDecl functionDecl) {
@ -550,6 +557,7 @@ public final class PbsFrontendCompiler {
private record ExecutableLoweringResult(
ReadOnlyList<IRBackendExecutableFunction> executableFunctions,
ReadOnlyList<CallableSignatureRef> callableSignatures) {
ReadOnlyList<CallableSignatureRef> callableSignatures,
ReadOnlyList<IntrinsicReference> intrinsicPool) {
}
}

View File

@ -3,6 +3,7 @@ package p.studio.compiler.backend.irvm;
public enum IRVMLoweringErrorCode {
LOWER_IRVM_EMPTY_EXECUTABLE_INPUT,
LOWER_IRVM_MISSING_CALLEE,
LOWER_IRVM_INVALID_INTRINSIC_ID,
LOWER_IRVM_CALL_ARG_SLOTS_MISMATCH,
LOWER_IRVM_CALL_RET_SLOTS_MISMATCH,
LOWER_IRVM_MISSING_JUMP_TARGET,

View File

@ -121,6 +121,11 @@ public class LowerToIRVMService {
}
case CALL_INTRINSIC -> {
final var intrinsic = instr.intrinsicCall();
if (intrinsic.intrinsicId() < 0 || intrinsic.intrinsicId() >= backend.getIntrinsicPool().size()) {
throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_INVALID_INTRINSIC_ID,
"invalid intrinsic id: " + intrinsic.intrinsicId());
}
instructions.add(new IRVMInstruction(IRVMOp.INTRINSIC, intrinsic.intrinsicId()));
operations.add(BytecodeEmitter.Operation.intrinsic(intrinsic.intrinsicId(), sourceSpan));
functionPc += IRVMOp.INTRINSIC.immediateSize() + 2;

View File

@ -5,6 +5,7 @@ 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 static org.junit.jupiter.api.Assertions.assertEquals;
@ -39,8 +40,9 @@ class LowerToIRVMServiceTest {
.executableFunctions(ReadOnlyList.from(
fn("main", "app", 10, ReadOnlyList.from(
callHost("gfx", "draw_pixel", 1, 2, 0),
callIntrinsic("core.color.pack", 1, 0x2001),
callIntrinsic("core.color.pack", 1, 0),
ret()))))
.intrinsicPool(ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1)))
.build();
final var lowered = new LowerToIRVMService().lower(backend);
@ -48,7 +50,7 @@ class LowerToIRVMServiceTest {
final var instructions = lowered.module().functions().getFirst().instructions();
assertEquals(IRVMOp.HOSTCALL, instructions.get(0).op());
assertEquals(IRVMOp.INTRINSIC, instructions.get(1).op());
assertEquals(0x2001, instructions.get(1).immediate());
assertEquals(0, instructions.get(1).immediate());
final var emissionOps = lowered.emissionPlan().functions().getFirst().operations();
assertEquals(p.studio.compiler.backend.bytecode.BytecodeEmitter.OperationKind.HOSTCALL, emissionOps.get(0).kind());
assertEquals(p.studio.compiler.backend.bytecode.BytecodeEmitter.OperationKind.INTRINSIC, emissionOps.get(1).kind());

View File

@ -11,6 +11,7 @@ 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.compiler.workspaces.stages.EmitBytecodePipelineStage;
import p.studio.compiler.workspaces.stages.LowerToIRVMPipelineStage;
import p.studio.compiler.workspaces.stages.OptimizeIRVMPipelineStage;
@ -141,8 +142,9 @@ class BackendGateIIntegrationTest {
void gateI_validIntrinsicPath() {
final var module = emitFromBackend(backendWithSingleFunction(
fn("main", ReadOnlyList.from(
callIntrinsic("core.color.pack", 1, 0x2000),
ret()))));
callIntrinsic("core.color.pack", 1, 0),
ret())),
ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1))));
final var check = compatibilityAdapter.check(module);
assertEquals(CompatibilityError.NONE, check.error());
@ -166,8 +168,15 @@ class BackendGateIIntegrationTest {
}
private IRBackend backendWithSingleFunction(final IRBackendExecutableFunction function) {
return backendWithSingleFunction(function, ReadOnlyList.empty());
}
private IRBackend backendWithSingleFunction(
final IRBackendExecutableFunction function,
final ReadOnlyList<IntrinsicReference> intrinsicPool) {
return IRBackend.builder()
.executableFunctions(ReadOnlyList.from(function))
.intrinsicPool(intrinsicPool)
.build();
}

View File

@ -4,6 +4,8 @@ import lombok.Builder;
import lombok.Getter;
import p.studio.compiler.source.tables.CallableSignatureRef;
import p.studio.compiler.source.tables.CallableTable;
import p.studio.compiler.source.tables.IntrinsicReference;
import p.studio.compiler.source.tables.IntrinsicTable;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList;
@ -19,6 +21,8 @@ public class IRBackend {
@Builder.Default
private final ReadOnlyList<CallableSignatureRef> callableSignatures = ReadOnlyList.empty();
@Builder.Default
private final ReadOnlyList<IntrinsicReference> intrinsicPool = ReadOnlyList.empty();
@Builder.Default
private final IRReservedMetadata reservedMetadata = IRReservedMetadata.empty();
public static IRBackendAggregator aggregator() {
@ -29,6 +33,7 @@ public class IRBackend {
private final ArrayList<IRFunction> functions = new ArrayList<>();
private final ArrayList<IRBackendExecutableFunction> executableFunctions = new ArrayList<>();
private final CallableTable callableTable = new CallableTable();
private final IntrinsicTable intrinsicTable = new IntrinsicTable();
private final ArrayList<IRReservedMetadata.HostMethodBinding> hostMethodBindings = new ArrayList<>();
private final ArrayList<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces = new ArrayList<>();
private final ArrayList<IRReservedMetadata.BuiltinConstSurface> builtinConstSurfaces = new ArrayList<>();
@ -40,8 +45,9 @@ public class IRBackend {
}
functions.addAll(backendFile.functions().asList());
final var callableRemap = reindexCallables(backendFile.callableSignatures());
final var intrinsicRemap = reindexIntrinsics(backendFile.intrinsicPool());
for (final var function : backendFile.executableFunctions()) {
executableFunctions.add(remapExecutableFunction(function, callableRemap));
executableFunctions.add(remapExecutableFunction(function, callableRemap, intrinsicRemap));
}
final var metadata = backendFile.reservedMetadata();
hostMethodBindings.addAll(metadata.hostMethodBindings().asList());
@ -63,7 +69,8 @@ public class IRBackend {
private IRBackendExecutableFunction remapExecutableFunction(
final IRBackendExecutableFunction function,
final int[] callableRemap) {
final int[] callableRemap,
final int[] intrinsicRemap) {
final var remappedInstructions = new ArrayList<IRBackendExecutableFunction.Instruction>(function.instructions().size());
for (final var instruction : function.instructions()) {
final Integer remappedCallee;
@ -72,13 +79,23 @@ public class IRBackend {
} else {
remappedCallee = remapCallableId(instruction.calleeCallableId(), callableRemap, "callee");
}
final IRBackendExecutableFunction.IntrinsicCallMetadata remappedIntrinsic;
if (instruction.kind() == IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC && instruction.intrinsicCall() != null) {
final var intrinsic = instruction.intrinsicCall();
remappedIntrinsic = new IRBackendExecutableFunction.IntrinsicCallMetadata(
intrinsic.canonicalName(),
intrinsic.canonicalVersion(),
remapIntrinsicId(intrinsic.intrinsicId(), intrinsicRemap));
} else {
remappedIntrinsic = instruction.intrinsicCall();
}
remappedInstructions.add(new IRBackendExecutableFunction.Instruction(
instruction.kind(),
instruction.calleeModuleKey(),
instruction.calleeCallableName(),
remappedCallee,
instruction.hostCall(),
instruction.intrinsicCall(),
remappedIntrinsic,
instruction.label(),
instruction.targetLabel(),
instruction.expectedArgSlots(),
@ -110,6 +127,26 @@ public class IRBackend {
return callableRemap[localCallableId];
}
private int[] reindexIntrinsics(final ReadOnlyList<IntrinsicReference> localIntrinsicPool) {
if (localIntrinsicPool == null || localIntrinsicPool.isEmpty()) {
return new int[0];
}
final var remap = new int[localIntrinsicPool.size()];
for (var i = 0; i < localIntrinsicPool.size(); i++) {
remap[i] = intrinsicTable.register(localIntrinsicPool.get(i)).getId();
}
return remap;
}
private int remapIntrinsicId(
final int localIntrinsicId,
final int[] intrinsicRemap) {
if (localIntrinsicId < 0 || localIntrinsicId >= intrinsicRemap.length) {
throw new IllegalArgumentException("invalid local intrinsic id: " + localIntrinsicId);
}
return intrinsicRemap[localIntrinsicId];
}
private ReadOnlyList<CallableSignatureRef> emitCallableSignatures() {
final var signatures = new ArrayList<CallableSignatureRef>(callableTable.size());
for (final var callableId : callableTable.identifiers()) {
@ -118,12 +155,21 @@ public class IRBackend {
return ReadOnlyList.wrap(signatures);
}
private ReadOnlyList<IntrinsicReference> emitIntrinsicPool() {
final var pool = new ArrayList<IntrinsicReference>(intrinsicTable.size());
for (final var intrinsicId : intrinsicTable.identifiers()) {
pool.add(intrinsicTable.get(intrinsicId));
}
return ReadOnlyList.wrap(pool);
}
public IRBackend emit() {
return IRBackend
.builder()
.functions(ReadOnlyList.wrap(functions))
.executableFunctions(ReadOnlyList.wrap(executableFunctions))
.callableSignatures(emitCallableSignatures())
.intrinsicPool(emitIntrinsicPool())
.reservedMetadata(new IRReservedMetadata(
ReadOnlyList.wrap(hostMethodBindings),
ReadOnlyList.wrap(builtinTypeSurfaces),
@ -139,6 +185,7 @@ public class IRBackend {
sb.append("IRBackend{functions=").append(functions.size())
.append(", executableFunctions=").append(executableFunctions.size())
.append(", callableSignatures=").append(callableSignatures.size())
.append(", intrinsicPool=").append(intrinsicPool.size())
.append(", hostBindings=").append(reservedMetadata.hostMethodBindings().size())
.append(", builtinTypes=").append(reservedMetadata.builtinTypeSurfaces().size())
.append(", builtinConsts=").append(reservedMetadata.builtinConstSurfaces().size())

View File

@ -2,6 +2,7 @@ package p.studio.compiler.models;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.compiler.source.tables.CallableSignatureRef;
import p.studio.compiler.source.tables.IntrinsicReference;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.Objects;
@ -11,29 +12,31 @@ public record IRBackendFile(
ReadOnlyList<IRFunction> functions,
ReadOnlyList<IRBackendExecutableFunction> executableFunctions,
IRReservedMetadata reservedMetadata,
ReadOnlyList<CallableSignatureRef> callableSignatures) {
ReadOnlyList<CallableSignatureRef> callableSignatures,
ReadOnlyList<IntrinsicReference> intrinsicPool) {
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;
callableSignatures = callableSignatures == null ? ReadOnlyList.empty() : callableSignatures;
intrinsicPool = intrinsicPool == null ? ReadOnlyList.empty() : intrinsicPool;
}
public IRBackendFile(
final FileId fileId,
final ReadOnlyList<IRFunction> functions) {
this(fileId, functions, ReadOnlyList.empty(), IRReservedMetadata.empty(), ReadOnlyList.empty());
this(fileId, functions, ReadOnlyList.empty(), IRReservedMetadata.empty(), ReadOnlyList.empty(), ReadOnlyList.empty());
}
public static IRBackendFile empty(final FileId fileId) {
return new IRBackendFile(fileId, ReadOnlyList.empty(), ReadOnlyList.empty(), IRReservedMetadata.empty(), ReadOnlyList.empty());
return new IRBackendFile(fileId, ReadOnlyList.empty(), ReadOnlyList.empty(), IRReservedMetadata.empty(), ReadOnlyList.empty(), ReadOnlyList.empty());
}
public IRBackendFile(
final FileId fileId,
final ReadOnlyList<IRFunction> functions,
final IRReservedMetadata reservedMetadata) {
this(fileId, functions, ReadOnlyList.empty(), reservedMetadata, ReadOnlyList.empty());
this(fileId, functions, ReadOnlyList.empty(), reservedMetadata, ReadOnlyList.empty(), ReadOnlyList.empty());
}
}

View File

@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test;
import p.studio.compiler.source.Span;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.compiler.source.tables.CallableSignatureRef;
import p.studio.compiler.source.tables.IntrinsicReference;
import p.studio.utilities.structures.ReadOnlyList;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -139,7 +140,8 @@ class IRBackendExecutableContractTest {
Span.none())),
Span.none())),
IRReservedMetadata.empty(),
ReadOnlyList.from(new CallableSignatureRef("app/main", "entry", 0, "() -> unit")));
ReadOnlyList.from(new CallableSignatureRef("app/main", "entry", 0, "() -> unit")),
ReadOnlyList.empty());
final var fileB = new IRBackendFile(
new FileId(2),
ReadOnlyList.empty(),
@ -164,7 +166,8 @@ class IRBackendExecutableContractTest {
Span.none())),
Span.none())),
IRReservedMetadata.empty(),
ReadOnlyList.from(new CallableSignatureRef("app/main", "aux", 0, "() -> unit")));
ReadOnlyList.from(new CallableSignatureRef("app/main", "aux", 0, "() -> unit")),
ReadOnlyList.empty());
final var aggregator = IRBackend.aggregator();
aggregator.merge(fileA);
@ -176,4 +179,69 @@ class IRBackendExecutableContractTest {
assertEquals("aux", backend.getExecutableFunctions().get(1).callableName());
assertEquals(2, backend.getCallableSignatures().size());
}
@Test
void aggregatorMustReindexIntrinsicsToBuildGlobalPool() {
final var fileA = new IRBackendFile(
new FileId(1),
ReadOnlyList.empty(),
ReadOnlyList.from(new IRBackendExecutableFunction(
new FileId(1),
"app/main",
"entry",
0,
0,
10,
0,
0,
0,
1,
ReadOnlyList.from(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC,
"",
"",
null,
new IRBackendExecutableFunction.IntrinsicCallMetadata("core.color.pack", 1, 0),
Span.none())),
Span.none())),
IRReservedMetadata.empty(),
ReadOnlyList.from(new CallableSignatureRef("app/main", "entry", 0, "() -> unit")),
ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1)));
final var fileB = new IRBackendFile(
new FileId(2),
ReadOnlyList.empty(),
ReadOnlyList.from(new IRBackendExecutableFunction(
new FileId(2),
"app/main",
"aux",
0,
11,
20,
0,
0,
0,
1,
ReadOnlyList.from(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC,
"",
"",
null,
new IRBackendExecutableFunction.IntrinsicCallMetadata("core.color.pack", 1, 0),
Span.none())),
Span.none())),
IRReservedMetadata.empty(),
ReadOnlyList.from(new CallableSignatureRef("app/main", "aux", 0, "() -> unit")),
ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1)));
final var aggregator = IRBackend.aggregator();
aggregator.merge(fileA);
aggregator.merge(fileB);
final var backend = aggregator.emit();
final var firstIntrinsicId = backend.getExecutableFunctions().get(0).instructions().getFirst().intrinsicCall().intrinsicId();
final var secondIntrinsicId = backend.getExecutableFunctions().get(1).instructions().getFirst().intrinsicCall().intrinsicId();
assertEquals(1, backend.getIntrinsicPool().size());
assertEquals(firstIntrinsicId, secondIntrinsicId);
assertEquals("core.color.pack", backend.getIntrinsicPool().getFirst().canonicalName());
}
}