diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java index cf60f4dd..3c69cc16 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java @@ -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(); final var callsites = new ArrayList(); 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 instructions, + final int returnSlots) { + if (instructions.isEmpty()) { + return Math.max(1, returnSlots); + } + + final var labelToIndex = new HashMap(); + 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(); + final var worklist = new ArrayDeque(); + final var visited = new HashSet(); + 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(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 incomingHeightByInstruction, + final ArrayDeque 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 executableFunctions, ReadOnlyList callableSignatures, ReadOnlyList intrinsicPool) { } + + private static final class ExecutableLoweringAnalysisException extends RuntimeException { + private ExecutableLoweringAnalysisException(final String message) { + super(message); + } + } }