From 541ada32a1f37f304da615ec2aed5d3e0347742a Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 9 Mar 2026 06:50:21 +0000 Subject: [PATCH] implements PR-05.1 --- .../compiler/pbs/PbsFrontendCompiler.java | 494 ++++++++++++------ .../compiler/pbs/PbsFrontendCompilerTest.java | 61 +++ 2 files changed, 408 insertions(+), 147 deletions(-) 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 64efd468..9c691f31 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 @@ -223,97 +223,27 @@ public final class PbsFrontendCompiler { if (functionCallableId == null) { continue; } - final var instructions = new ArrayList(); - final var callsites = new ArrayList(); - collectCallsFromBlock(fn.body(), callsites); - for (final var callExpr : callsites) { - final var calleeName = extractSimpleCalleeName(callExpr.callee()); - if (calleeName == null || calleeName.isBlank()) { - diagnostics.error( - DiagnosticPhase.STATIC_SEMANTICS, - PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(), - PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(), - Map.of(), - "executable lowering requires resolvable callee identity", - callExpr.span()); - continue; - } - final var host = hostByMethodName.get(calleeName); - if (host != null) { - instructions.add(new IRBackendExecutableFunction.Instruction( - IRBackendExecutableFunction.InstructionKind.CALL_HOST, - "", - "", - new IRBackendExecutableFunction.HostCallMetadata( - host.abiModule(), - host.abiMethod(), - host.abiVersion(), - callExpr.arguments().size(), - 0), - null, - callExpr.span())); - continue; - } - - final var intrinsic = intrinsicByMethodName.get(calleeName); - if (intrinsic != null) { - instructions.add(new IRBackendExecutableFunction.Instruction( - IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC, - "", - "", - null, - new IRBackendExecutableFunction.IntrinsicCallMetadata( - intrinsic.canonicalName(), - intrinsic.canonicalVersion(), - intrinsicIdTable - .register(intrinsic.canonicalName(), intrinsic.canonicalVersion()) - ), - callExpr.span())); - continue; - } - - final var candidateCallableIds = callableIdsByNameAndArity.get( - new CallableResolutionKey(nameTable.register(calleeName), callExpr.arguments().size())); - if (candidateCallableIds == null || candidateCallableIds.isEmpty()) { - diagnostics.error( - DiagnosticPhase.STATIC_SEMANTICS, - PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(), - PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(), - Map.of(), - "executable lowering requires resolvable callee identity", - callExpr.span()); - continue; - } - if (candidateCallableIds.size() > 1) { - diagnostics.error( - DiagnosticPhase.STATIC_SEMANTICS, - PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(), - PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(), - Map.of(), - "executable lowering found ambiguous callable identity", - callExpr.span()); - continue; - } - final var calleeCallableId = candidateCallableIds.getFirst(); + final var loweringContext = new ExecutableLoweringContext( + normalizedModuleKey, + diagnostics, + nameTable, + hostByMethodName, + intrinsicByMethodName, + callableIdsByNameAndArity, + returnSlotsByCallableId, + intrinsicIdTable); + final var terminated = lowerBlock(fn.body(), loweringContext); + final var instructions = loweringContext.instructions(); + if (!terminated) { instructions.add(new IRBackendExecutableFunction.Instruction( - IRBackendExecutableFunction.InstructionKind.CALL_FUNC, - normalizedModuleKey, - calleeName, - calleeCallableId, + IRBackendExecutableFunction.InstructionKind.RET, + "", + "", null, null, - callExpr.arguments().size(), - returnSlotsByCallableId.get(calleeCallableId), - callExpr.span())); + null, + fn.span())); } - instructions.add(new IRBackendExecutableFunction.Instruction( - IRBackendExecutableFunction.InstructionKind.RET, - "", - "", - null, - null, - null, - fn.span())); final var returnSlots = returnSlotsFor(fn); final int maxStackSlots; @@ -449,127 +379,192 @@ public final class PbsFrontendCompiler { return (int) value; } - private void collectCallsFromBlock( + private boolean lowerBlock( final PbsAst.Block block, - final List output) { + final ExecutableLoweringContext context) { if (block == null) { - return; + return false; } + var terminated = false; for (final var statement : block.statements()) { - collectCallsFromStatement(statement, output); + if (terminated) { + break; + } + terminated = lowerStatement(statement, context); } - if (block.tailExpression() != null) { - collectCallsFromExpression(block.tailExpression(), output); + if (!terminated && block.tailExpression() != null) { + lowerExpression(block.tailExpression(), context); } + return terminated; } - private void collectCallsFromStatement( + private boolean lowerStatement( final PbsAst.Statement statement, - final List output) { + final ExecutableLoweringContext context) { if (statement == null) { - return; + return false; } switch (statement) { - case PbsAst.LetStatement letStatement -> collectCallsFromExpression(letStatement.initializer(), output); - case PbsAst.AssignStatement assignStatement -> collectCallsFromExpression(assignStatement.value(), output); + case PbsAst.LetStatement letStatement -> lowerExpression(letStatement.initializer(), context); + case PbsAst.AssignStatement assignStatement -> lowerExpression(assignStatement.value(), context); case PbsAst.ReturnStatement returnStatement -> { if (returnStatement.value() != null) { - collectCallsFromExpression(returnStatement.value(), output); + lowerExpression(returnStatement.value(), context); } + context.instructions().add(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.RET, + "", + "", + null, + null, + null, + returnStatement.span())); + return true; } case PbsAst.IfStatement ifStatement -> { - collectCallsFromExpression(ifStatement.condition(), output); - collectCallsFromBlock(ifStatement.thenBlock(), output); + lowerExpression(ifStatement.condition(), context); + final var elseLabel = context.nextLabel("if_else"); + final var endLabel = context.nextLabel("if_end"); + emitJump(IRBackendExecutableFunction.InstructionKind.JMP_IF_FALSE, elseLabel, ifStatement.span(), context); + final var thenTerminated = lowerBlock(ifStatement.thenBlock(), context); + if (!thenTerminated) { + emitJump(IRBackendExecutableFunction.InstructionKind.JMP, endLabel, ifStatement.span(), context); + } + emitLabel(elseLabel, ifStatement.span(), context); + final boolean elseTerminated; if (ifStatement.elseIf() != null) { - collectCallsFromStatement(ifStatement.elseIf(), output); + elseTerminated = lowerStatement(ifStatement.elseIf(), context); + } else if (ifStatement.elseBlock() != null) { + elseTerminated = lowerBlock(ifStatement.elseBlock(), context); + } else { + elseTerminated = false; } - if (ifStatement.elseBlock() != null) { - collectCallsFromBlock(ifStatement.elseBlock(), output); + if (!thenTerminated || !elseTerminated) { + emitLabel(endLabel, ifStatement.span(), context); } + return (ifStatement.elseIf() != null || ifStatement.elseBlock() != null) + && thenTerminated + && elseTerminated; } case PbsAst.ForStatement forStatement -> { - collectCallsFromExpression(forStatement.fromExpression(), output); - collectCallsFromExpression(forStatement.untilExpression(), output); + lowerExpression(forStatement.fromExpression(), context); + final var loopStart = context.nextLabel("for_start"); + final var loopExit = context.nextLabel("for_exit"); + final var loopContinue = context.nextLabel("for_continue"); + emitLabel(loopStart, forStatement.span(), context); + lowerExpression(forStatement.untilExpression(), context); + emitJump(IRBackendExecutableFunction.InstructionKind.JMP_IF_FALSE, loopExit, forStatement.span(), context); + context.pushLoop(loopContinue, loopExit); + lowerBlock(forStatement.body(), context); + context.popLoop(); + emitLabel(loopContinue, forStatement.span(), context); if (forStatement.stepExpression() != null) { - collectCallsFromExpression(forStatement.stepExpression(), output); + lowerExpression(forStatement.stepExpression(), context); } - collectCallsFromBlock(forStatement.body(), output); + emitJump(IRBackendExecutableFunction.InstructionKind.JMP, loopStart, forStatement.span(), context); + emitLabel(loopExit, forStatement.span(), context); } case PbsAst.WhileStatement whileStatement -> { - collectCallsFromExpression(whileStatement.condition(), output); - collectCallsFromBlock(whileStatement.body(), output); + final var loopStart = context.nextLabel("while_start"); + final var loopExit = context.nextLabel("while_exit"); + emitLabel(loopStart, whileStatement.span(), context); + lowerExpression(whileStatement.condition(), context); + emitJump(IRBackendExecutableFunction.InstructionKind.JMP_IF_FALSE, loopExit, whileStatement.span(), context); + context.pushLoop(loopStart, loopExit); + lowerBlock(whileStatement.body(), context); + context.popLoop(); + emitJump(IRBackendExecutableFunction.InstructionKind.JMP, loopStart, whileStatement.span(), context); + emitLabel(loopExit, whileStatement.span(), context); } - case PbsAst.ExpressionStatement expressionStatement -> - collectCallsFromExpression(expressionStatement.expression(), output); - case PbsAst.BreakStatement ignored -> { + case PbsAst.ExpressionStatement expressionStatement -> lowerExpression(expressionStatement.expression(), context); + case PbsAst.BreakStatement breakStatement -> { + if (context.loopTargets().isEmpty()) { + reportUnsupportedLowering("break statement outside loop", breakStatement.span(), context); + return true; + } + emitJump(IRBackendExecutableFunction.InstructionKind.JMP, context.loopTargets().peek().breakLabel(), breakStatement.span(), context); + return true; } - case PbsAst.ContinueStatement ignored -> { + case PbsAst.ContinueStatement continueStatement -> { + if (context.loopTargets().isEmpty()) { + reportUnsupportedLowering("continue statement outside loop", continueStatement.span(), context); + return true; + } + emitJump(IRBackendExecutableFunction.InstructionKind.JMP, context.loopTargets().peek().continueLabel(), continueStatement.span(), context); + return true; } } + return false; } - private void collectCallsFromExpression( + private void lowerExpression( final PbsAst.Expression expression, - final List output) { + final ExecutableLoweringContext context) { if (expression == null) { return; } switch (expression) { case PbsAst.CallExpr callExpr -> { - output.add(callExpr); - collectCallsFromExpression(callExpr.callee(), output); + lowerExpression(callExpr.callee(), context); for (final var arg : callExpr.arguments()) { - collectCallsFromExpression(arg, output); + lowerExpression(arg, context); } + lowerCallsite(callExpr, context); } case PbsAst.ApplyExpr applyExpr -> { - collectCallsFromExpression(applyExpr.callee(), output); - collectCallsFromExpression(applyExpr.argument(), output); + lowerExpression(applyExpr.callee(), context); + lowerExpression(applyExpr.argument(), context); } case PbsAst.BinaryExpr binaryExpr -> { - collectCallsFromExpression(binaryExpr.left(), output); - collectCallsFromExpression(binaryExpr.right(), output); + lowerExpression(binaryExpr.left(), context); + lowerExpression(binaryExpr.right(), context); } - case PbsAst.UnaryExpr unaryExpr -> collectCallsFromExpression(unaryExpr.expression(), output); + case PbsAst.UnaryExpr unaryExpr -> lowerExpression(unaryExpr.expression(), context); case PbsAst.ElseExpr elseExpr -> { - collectCallsFromExpression(elseExpr.optionalExpression(), output); - collectCallsFromExpression(elseExpr.fallbackExpression(), output); + lowerExpression(elseExpr.optionalExpression(), context); + lowerExpression(elseExpr.fallbackExpression(), context); } case PbsAst.IfExpr ifExpr -> { - collectCallsFromExpression(ifExpr.condition(), output); - collectCallsFromBlock(ifExpr.thenBlock(), output); - collectCallsFromExpression(ifExpr.elseExpression(), output); + lowerExpression(ifExpr.condition(), context); + final var elseLabel = context.nextLabel("ifexpr_else"); + final var endLabel = context.nextLabel("ifexpr_end"); + emitJump(IRBackendExecutableFunction.InstructionKind.JMP_IF_FALSE, elseLabel, ifExpr.span(), context); + final var thenTerminated = lowerBlock(ifExpr.thenBlock(), context); + if (!thenTerminated) { + emitJump(IRBackendExecutableFunction.InstructionKind.JMP, endLabel, ifExpr.span(), context); + } + emitLabel(elseLabel, ifExpr.span(), context); + lowerExpression(ifExpr.elseExpression(), context); + if (!thenTerminated) { + emitLabel(endLabel, ifExpr.span(), context); + } } case PbsAst.SwitchExpr switchExpr -> { - collectCallsFromExpression(switchExpr.selector(), output); - for (final var arm : switchExpr.arms()) { - collectCallsFromBlock(arm.block(), output); - } + lowerExpression(switchExpr.selector(), context); + reportUnsupportedLowering("switch expression is not lowerable in executable lowering v1", switchExpr.span(), context); } case PbsAst.HandleExpr handleExpr -> { - collectCallsFromExpression(handleExpr.value(), output); - for (final var arm : handleExpr.arms()) { - collectCallsFromBlock(arm.block(), output); - } + lowerExpression(handleExpr.value(), context); + reportUnsupportedLowering("handle expression is not lowerable in executable lowering v1", handleExpr.span(), context); } - case PbsAst.AsExpr asExpr -> collectCallsFromExpression(asExpr.expression(), output); - case PbsAst.MemberExpr memberExpr -> collectCallsFromExpression(memberExpr.receiver(), output); - case PbsAst.PropagateExpr propagateExpr -> collectCallsFromExpression(propagateExpr.expression(), output); - case PbsAst.GroupExpr groupExpr -> collectCallsFromExpression(groupExpr.expression(), output); + case PbsAst.AsExpr asExpr -> lowerExpression(asExpr.expression(), context); + case PbsAst.MemberExpr memberExpr -> lowerExpression(memberExpr.receiver(), context); + case PbsAst.PropagateExpr propagateExpr -> lowerExpression(propagateExpr.expression(), context); + case PbsAst.GroupExpr groupExpr -> lowerExpression(groupExpr.expression(), context); case PbsAst.NewExpr newExpr -> { for (final var arg : newExpr.arguments()) { - collectCallsFromExpression(arg, output); + lowerExpression(arg, context); } } - case PbsAst.BindExpr bindExpr -> collectCallsFromExpression(bindExpr.contextExpression(), output); - case PbsAst.SomeExpr someExpr -> collectCallsFromExpression(someExpr.value(), output); - case PbsAst.OkExpr okExpr -> collectCallsFromExpression(okExpr.value(), output); + case PbsAst.BindExpr bindExpr -> lowerExpression(bindExpr.contextExpression(), context); + case PbsAst.SomeExpr someExpr -> lowerExpression(someExpr.value(), context); + case PbsAst.OkExpr okExpr -> lowerExpression(okExpr.value(), context); case PbsAst.TupleExpr tupleExpr -> { for (final var item : tupleExpr.items()) { - collectCallsFromExpression(item.expression(), output); + lowerExpression(item.expression(), context); } } - case PbsAst.BlockExpr blockExpr -> collectCallsFromBlock(blockExpr.block(), output); + case PbsAst.BlockExpr blockExpr -> lowerBlock(blockExpr.block(), context); case PbsAst.IdentifierExpr ignored -> { } case PbsAst.IntLiteralExpr ignored -> { @@ -593,6 +588,119 @@ public final class PbsFrontendCompiler { } } + private void lowerCallsite( + final PbsAst.CallExpr callExpr, + final ExecutableLoweringContext context) { + final var calleeName = extractSimpleCalleeName(callExpr.callee()); + if (calleeName == null || calleeName.isBlank()) { + reportUnsupportedLowering("executable lowering requires resolvable callee identity", callExpr.span(), context); + return; + } + final var host = context.hostByMethodName().get(calleeName); + if (host != null) { + context.instructions().add(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.CALL_HOST, + "", + "", + new IRBackendExecutableFunction.HostCallMetadata( + host.abiModule(), + host.abiMethod(), + host.abiVersion(), + callExpr.arguments().size(), + 0), + null, + callExpr.span())); + return; + } + + final var intrinsic = context.intrinsicByMethodName().get(calleeName); + if (intrinsic != null) { + context.instructions().add(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC, + "", + "", + null, + new IRBackendExecutableFunction.IntrinsicCallMetadata( + intrinsic.canonicalName(), + intrinsic.canonicalVersion(), + context.intrinsicIdTable().register(intrinsic.canonicalName(), intrinsic.canonicalVersion())), + callExpr.span())); + return; + } + + final var candidateCallableIds = context.callableIdsByNameAndArity().get( + new CallableResolutionKey(context.nameTable().register(calleeName), callExpr.arguments().size())); + if (candidateCallableIds == null || candidateCallableIds.isEmpty()) { + reportUnsupportedLowering("executable lowering requires resolvable callee identity", callExpr.span(), context); + return; + } + if (candidateCallableIds.size() > 1) { + reportUnsupportedLowering("executable lowering found ambiguous callable identity", callExpr.span(), context); + return; + } + final var calleeCallableId = candidateCallableIds.getFirst(); + context.instructions().add(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.CALL_FUNC, + context.moduleKey(), + calleeName, + calleeCallableId, + null, + null, + callExpr.arguments().size(), + context.returnSlotsByCallableId().get(calleeCallableId), + callExpr.span())); + } + + private void emitJump( + final IRBackendExecutableFunction.InstructionKind jumpKind, + final String targetLabel, + final p.studio.compiler.source.Span span, + final ExecutableLoweringContext context) { + context.instructions().add(new IRBackendExecutableFunction.Instruction( + jumpKind, + "", + "", + null, + null, + null, + "", + targetLabel, + null, + null, + span)); + } + + private void emitLabel( + final String label, + final p.studio.compiler.source.Span span, + final ExecutableLoweringContext context) { + context.instructions().add(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.LABEL, + "", + "", + null, + null, + null, + label, + "", + null, + null, + span)); + } + + private void reportUnsupportedLowering( + final String message, + final p.studio.compiler.source.Span span, + final ExecutableLoweringContext context) { + context.diagnostics().error( + DiagnosticPhase.STATIC_SEMANTICS, + PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(), + PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(), + Map.of(), + message, + span); + } + private String extractSimpleCalleeName(final PbsAst.Expression callee) { return switch (callee) { case PbsAst.IdentifierExpr identifierExpr -> identifierExpr.name(); @@ -700,6 +808,98 @@ public final class PbsFrontendCompiler { } } + private static final class ExecutableLoweringContext { + private final String moduleKey; + private final DiagnosticSink diagnostics; + private final NameTable nameTable; + private final Map hostByMethodName; + private final Map intrinsicByMethodName; + private final Map> callableIdsByNameAndArity; + private final Map returnSlotsByCallableId; + private final IntrinsicTable intrinsicIdTable; + private final ArrayList instructions = new ArrayList<>(); + private final ArrayDeque loopTargets = new ArrayDeque<>(); + private int nextLabelId = 0; + + private ExecutableLoweringContext( + final String moduleKey, + final DiagnosticSink diagnostics, + final NameTable nameTable, + final Map hostByMethodName, + final Map intrinsicByMethodName, + final Map> callableIdsByNameAndArity, + final Map returnSlotsByCallableId, + final IntrinsicTable intrinsicIdTable) { + this.moduleKey = moduleKey; + this.diagnostics = diagnostics; + this.nameTable = nameTable; + this.hostByMethodName = hostByMethodName; + this.intrinsicByMethodName = intrinsicByMethodName; + this.callableIdsByNameAndArity = callableIdsByNameAndArity; + this.returnSlotsByCallableId = returnSlotsByCallableId; + this.intrinsicIdTable = intrinsicIdTable; + } + + private String moduleKey() { + return moduleKey; + } + + private DiagnosticSink diagnostics() { + return diagnostics; + } + + private NameTable nameTable() { + return nameTable; + } + + private Map hostByMethodName() { + return hostByMethodName; + } + + private Map intrinsicByMethodName() { + return intrinsicByMethodName; + } + + private Map> callableIdsByNameAndArity() { + return callableIdsByNameAndArity; + } + + private Map returnSlotsByCallableId() { + return returnSlotsByCallableId; + } + + private IntrinsicTable intrinsicIdTable() { + return intrinsicIdTable; + } + + private ArrayList instructions() { + return instructions; + } + + private ArrayDeque loopTargets() { + return loopTargets; + } + + private void pushLoop( + final String continueLabel, + final String breakLabel) { + loopTargets.push(new LoopTargets(continueLabel, breakLabel)); + } + + private void popLoop() { + loopTargets.pop(); + } + + private String nextLabel(final String prefix) { + return "__" + prefix + "_" + nextLabelId++; + } + } + + private record LoopTargets( + String continueLabel, + String breakLabel) { + } + private record ExecutableLoweringResult( ReadOnlyList executableFunctions, ReadOnlyList callableSignatures, diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java index ce15d16b..0e0c09dc 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java @@ -123,6 +123,67 @@ class PbsFrontendCompilerTest { assertEquals("c", executableA.instructions().get(1).calleeCallableName()); } + @Test + void shouldStopLoweringReachablePathAfterReturn() { + final var source = """ + fn a() -> int { return 1; } + fn b() -> int { return 2; } + fn main() -> int { + return a(); + b(); + } + """; + + final var diagnostics = DiagnosticSink.empty(); + final var compiler = new PbsFrontendCompiler(); + final var fileBackend = compiler.compileFile(new FileId(100), source, diagnostics); + + assertTrue(diagnostics.isEmpty(), "Valid program should not report diagnostics"); + final var executableMain = fileBackend.executableFunctions().stream() + .filter(fn -> fn.callableName().equals("main")) + .findFirst() + .orElseThrow(); + assertEquals(2, executableMain.instructions().size()); + assertEquals("a", executableMain.instructions().get(0).calleeCallableName()); + assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.RET, executableMain.instructions().get(1).kind()); + } + + @Test + void shouldEmitControlFlowInstructionsForIfAndWhile() { + final var source = """ + fn cond() -> bool { return true; } + fn t() -> int { return 1; } + fn f() -> int { return 2; } + fn loopBody() -> void { return; } + fn main() -> int { + while cond() { + break; + } + if cond() { + return t(); + } else { + return f(); + } + } + """; + + final var diagnostics = DiagnosticSink.empty(); + final var compiler = new PbsFrontendCompiler(); + final var fileBackend = compiler.compileFile(new FileId(101), source, diagnostics); + + assertTrue(diagnostics.isEmpty(), "Valid program should not report diagnostics"); + final var executableMain = fileBackend.executableFunctions().stream() + .filter(fn -> fn.callableName().equals("main")) + .findFirst() + .orElseThrow(); + assertTrue(executableMain.instructions().stream().anyMatch(i -> + i.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.LABEL)); + assertTrue(executableMain.instructions().stream().anyMatch(i -> + i.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.JMP)); + assertTrue(executableMain.instructions().stream().anyMatch(i -> + i.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.JMP_IF_FALSE)); + } + @Test void shouldNotLowerWhenSyntaxErrorsExist() { final var source = """