implements PR-O4.3

This commit is contained in:
bQUARKz 2026-03-07 19:39:24 +00:00
parent 3449771f33
commit 9e9ce76c6c
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8

View File

@ -23,9 +23,12 @@ import p.studio.compiler.source.tables.IntrinsicTable;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public final class PbsFrontendCompiler {
private final PbsDeclarationSemanticsValidator declarationSemanticsValidator = new PbsDeclarationSemanticsValidator();
@ -198,13 +201,6 @@ public final class PbsFrontendCompiler {
final var instructions = new ArrayList<IRBackendExecutableFunction.Instruction>();
final var callsites = new ArrayList<PbsAst.CallExpr>();
collectCallsFromBlock(fn.body(), callsites);
callsites.sort((a, b) -> {
final var deltaStart = Long.compare(a.span().getStart(), b.span().getStart());
if (deltaStart != 0) {
return deltaStart;
}
return Long.compare(a.span().getEnd(), b.span().getEnd());
});
for (final var callExpr : callsites) {
final var calleeName = extractSimpleCalleeName(callExpr.callee());
if (calleeName == null || calleeName.isBlank()) {
@ -294,6 +290,19 @@ public final class PbsFrontendCompiler {
fn.span()));
final var returnSlots = returnSlotsFor(fn);
final int maxStackSlots;
try {
maxStackSlots = analyzeMaxStackSlots(ReadOnlyList.wrap(instructions), returnSlots);
} catch (ExecutableLoweringAnalysisException e) {
diagnostics.error(
DiagnosticPhase.STATIC_SEMANTICS,
PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(),
PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(),
Map.of(),
e.getMessage(),
fn.span());
continue;
}
final var start = safeToInt(fn.span().getStart());
final var end = safeToInt(fn.span().getEnd());
executableFunctions.add(new IRBackendExecutableFunction(
@ -306,7 +315,7 @@ public final class PbsFrontendCompiler {
fn.parameters().size(),
0,
returnSlots,
Math.max(4, fn.parameters().size() + 2),
maxStackSlots,
ReadOnlyList.wrap(instructions),
fn.span()));
}
@ -555,9 +564,112 @@ public final class PbsFrontendCompiler {
};
}
private int analyzeMaxStackSlots(
final ReadOnlyList<IRBackendExecutableFunction.Instruction> instructions,
final int returnSlots) {
if (instructions.isEmpty()) {
return Math.max(1, returnSlots);
}
final var labelToIndex = new HashMap<String, Integer>();
for (var i = 0; i < instructions.size(); i++) {
final var instruction = instructions.get(i);
if (instruction.kind() == IRBackendExecutableFunction.InstructionKind.LABEL) {
if (labelToIndex.putIfAbsent(instruction.label(), i) != null) {
throw new ExecutableLoweringAnalysisException("duplicate label during stack analysis: " + instruction.label());
}
}
}
final var incomingHeightByInstruction = new HashMap<Integer, Integer>();
final var worklist = new ArrayDeque<Integer>();
final var visited = new HashSet<Integer>();
incomingHeightByInstruction.put(0, 0);
worklist.add(0);
var maxHeight = 0;
while (!worklist.isEmpty()) {
final var index = worklist.removeFirst();
visited.add(index);
final var instruction = instructions.get(index);
var outHeight = incomingHeightByInstruction.get(index);
switch (instruction.kind()) {
case CALL_FUNC -> outHeight += instruction.expectedRetSlots() == null ? 0 : instruction.expectedRetSlots();
case CALL_HOST -> outHeight += instruction.hostCall() == null ? 0 : instruction.hostCall().retSlots();
case CALL_INTRINSIC, HALT, LABEL, JMP, RET -> {
}
case JMP_IF_TRUE, JMP_IF_FALSE -> {
if (outHeight <= 0) {
throw new ExecutableLoweringAnalysisException("conditional jump requires stack height > 0");
}
outHeight -= 1;
}
}
maxHeight = Math.max(maxHeight, outHeight);
if (instruction.kind() == IRBackendExecutableFunction.InstructionKind.RET
|| instruction.kind() == IRBackendExecutableFunction.InstructionKind.HALT) {
continue;
}
final var successors = new ArrayList<Integer>(2);
if (instruction.kind() == IRBackendExecutableFunction.InstructionKind.JMP
|| instruction.kind() == IRBackendExecutableFunction.InstructionKind.JMP_IF_TRUE
|| instruction.kind() == IRBackendExecutableFunction.InstructionKind.JMP_IF_FALSE) {
final var targetIndex = labelToIndex.get(instruction.targetLabel());
if (targetIndex == null) {
throw new ExecutableLoweringAnalysisException("missing jump target label: " + instruction.targetLabel());
}
successors.add(targetIndex);
if (instruction.kind() == IRBackendExecutableFunction.InstructionKind.JMP) {
mergeIncomingHeights(incomingHeightByInstruction, worklist, targetIndex, outHeight);
continue;
}
}
final var nextIndex = index + 1;
if (nextIndex >= instructions.size()) {
throw new ExecutableLoweringAnalysisException("reachable path ends without terminal instruction");
}
successors.add(nextIndex);
for (final var successor : successors) {
mergeIncomingHeights(incomingHeightByInstruction, worklist, successor, outHeight);
}
}
if (!visited.contains(instructions.size() - 1)) {
throw new ExecutableLoweringAnalysisException("entrypoint does not reach function tail");
}
return Math.max(1, Math.max(maxHeight, returnSlots));
}
private void mergeIncomingHeights(
final Map<Integer, Integer> incomingHeightByInstruction,
final ArrayDeque<Integer> worklist,
final int targetIndex,
final int newHeight) {
final var existingHeight = incomingHeightByInstruction.get(targetIndex);
if (existingHeight == null) {
incomingHeightByInstruction.put(targetIndex, newHeight);
worklist.add(targetIndex);
return;
}
if (existingHeight != newHeight) {
throw new ExecutableLoweringAnalysisException(
"stack mismatch at join: existing=%d new=%d".formatted(existingHeight, newHeight));
}
}
private record ExecutableLoweringResult(
ReadOnlyList<IRBackendExecutableFunction> executableFunctions,
ReadOnlyList<CallableSignatureRef> callableSignatures,
ReadOnlyList<IntrinsicReference> intrinsicPool) {
}
private static final class ExecutableLoweringAnalysisException extends RuntimeException {
private ExecutableLoweringAnalysisException(final String message) {
super(message);
}
}
}