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 1a56c5d8..517b8b18 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 @@ -287,6 +287,10 @@ public final class PbsFrontendCompiler { if (functionCallableId == null) { continue; } + final var localSlotByNameId = new HashMap(); + for (var paramIndex = 0; paramIndex < fn.parameters().size(); paramIndex++) { + localSlotByNameId.put(nameTable.register(fn.parameters().get(paramIndex).name()), paramIndex); + } final var loweringContext = new ExecutableLoweringContext( normalizedModuleKey, diagnostics, @@ -295,7 +299,8 @@ public final class PbsFrontendCompiler { intrinsicByMethodName, callableIdsByNameAndArity, returnSlotsByCallableId, - intrinsicIdTable); + intrinsicIdTable, + localSlotByNameId); final var terminated = lowerBlock(fn.body(), loweringContext); final var instructions = loweringContext.instructions(); if (!terminated) { @@ -618,7 +623,7 @@ public final class PbsFrontendCompiler { } switch (expression) { case PbsAst.CallExpr callExpr -> { - lowerExpression(callExpr.callee(), context); + lowerCallsiteReceiver(callExpr.callee(), context); for (final var arg : callExpr.arguments()) { lowerExpression(arg, context); } @@ -678,16 +683,27 @@ public final class PbsFrontendCompiler { } } case PbsAst.BlockExpr blockExpr -> lowerBlock(blockExpr.block(), context); - case PbsAst.IdentifierExpr ignored -> { + case PbsAst.IdentifierExpr identifierExpr -> { + final var slot = context.localSlotByNameId().get(context.nameTable().register(identifierExpr.name())); + if (slot != null) { + emitGetLocal(slot, identifierExpr.span(), context); + } } - case PbsAst.IntLiteralExpr ignored -> { + case PbsAst.IntLiteralExpr intLiteralExpr -> { + if (intLiteralExpr.value() < Integer.MIN_VALUE || intLiteralExpr.value() > Integer.MAX_VALUE) { + reportUnsupportedLowering( + "int literal exceeds i32 lowering range: " + intLiteralExpr.value(), + intLiteralExpr.span(), + context); + return; + } + emitPushI32((int) intLiteralExpr.value(), intLiteralExpr.span(), context); } case PbsAst.FloatLiteralExpr ignored -> { } case PbsAst.BoundedLiteralExpr ignored -> { } - case PbsAst.StringLiteralExpr ignored -> { - } + case PbsAst.StringLiteralExpr stringLiteralExpr -> emitPushConst(stringLiteralExpr.value(), stringLiteralExpr.span(), context); case PbsAst.BoolLiteralExpr ignored -> { } case PbsAst.ThisExpr ignored -> { @@ -701,6 +717,20 @@ public final class PbsFrontendCompiler { } } + private void lowerCallsiteReceiver( + final PbsAst.Expression callee, + final ExecutableLoweringContext context) { + if (callee == null) { + return; + } + switch (callee) { + case PbsAst.MemberExpr memberExpr -> lowerExpression(memberExpr.receiver(), context); + case PbsAst.GroupExpr groupExpr -> lowerCallsiteReceiver(groupExpr.expression(), context); + default -> { + } + } + } + private void lowerCallsite( final PbsAst.CallExpr callExpr, final ExecutableLoweringContext context) { @@ -920,6 +950,60 @@ public final class PbsFrontendCompiler { span)); } + private void emitPushI32( + final int value, + final p.studio.compiler.source.Span span, + final ExecutableLoweringContext context) { + context.instructions().add(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.PUSH_I32, + "", + "", + null, + null, + null, + Integer.toString(value), + "", + null, + null, + span)); + } + + private void emitPushConst( + final String value, + final p.studio.compiler.source.Span span, + final ExecutableLoweringContext context) { + context.instructions().add(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.PUSH_CONST, + "", + "", + null, + null, + null, + value == null ? "" : value, + "", + null, + null, + span)); + } + + private void emitGetLocal( + final int slot, + final p.studio.compiler.source.Span span, + final ExecutableLoweringContext context) { + context.instructions().add(new IRBackendExecutableFunction.Instruction( + IRBackendExecutableFunction.InstructionKind.GET_LOCAL, + "", + "", + null, + null, + null, + "", + "", + slot, + null, + span)); + } + private void reportUnsupportedLowering( final String message, final p.studio.compiler.source.Span span, @@ -964,9 +1048,34 @@ public final class PbsFrontendCompiler { 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 -> outHeight += instruction.expectedRetSlots() == null ? 0 : instruction.expectedRetSlots(); + case PUSH_I32, PUSH_CONST, GET_LOCAL -> outHeight += 1; + case CALL_FUNC -> { + final var argSlots = instruction.expectedArgSlots() == null ? 0 : instruction.expectedArgSlots(); + final var retSlots = instruction.expectedRetSlots() == null ? 0 : instruction.expectedRetSlots(); + if (outHeight < argSlots) { + throw new ExecutableLoweringAnalysisException( + "stack underflow at call_func: need=%d have=%d".formatted(argSlots, outHeight)); + } + outHeight = outHeight - argSlots + retSlots; + } + case CALL_HOST -> { + final var argSlots = instruction.hostCall() == null ? 0 : instruction.hostCall().argSlots(); + final var retSlots = instruction.hostCall() == null ? 0 : instruction.hostCall().retSlots(); + if (outHeight < argSlots) { + throw new ExecutableLoweringAnalysisException( + "stack underflow at call_host: need=%d have=%d".formatted(argSlots, outHeight)); + } + outHeight = outHeight - argSlots + retSlots; + } + case CALL_INTRINSIC -> { + final var argSlots = instruction.expectedArgSlots() == null ? 0 : instruction.expectedArgSlots(); + final var retSlots = instruction.expectedRetSlots() == null ? 0 : instruction.expectedRetSlots(); + if (outHeight < argSlots) { + throw new ExecutableLoweringAnalysisException( + "stack underflow at call_intrinsic: need=%d have=%d".formatted(argSlots, outHeight)); + } + outHeight = outHeight - argSlots + retSlots; + } case HALT, LABEL, JMP, RET -> { } case JMP_IF_TRUE, JMP_IF_FALSE -> { @@ -1040,6 +1149,7 @@ public final class PbsFrontendCompiler { private final Map> callableIdsByNameAndArity; private final Map returnSlotsByCallableId; private final IntrinsicTable intrinsicIdTable; + private final Map localSlotByNameId; private final ArrayList instructions = new ArrayList<>(); private final ArrayDeque loopTargets = new ArrayDeque<>(); private int nextLabelId = 0; @@ -1052,7 +1162,8 @@ public final class PbsFrontendCompiler { final Map> intrinsicByMethodName, final Map> callableIdsByNameAndArity, final Map returnSlotsByCallableId, - final IntrinsicTable intrinsicIdTable) { + final IntrinsicTable intrinsicIdTable, + final Map localSlotByNameId) { this.moduleKey = moduleKey; this.diagnostics = diagnostics; this.nameTable = nameTable; @@ -1061,6 +1172,7 @@ public final class PbsFrontendCompiler { this.callableIdsByNameAndArity = callableIdsByNameAndArity; this.returnSlotsByCallableId = returnSlotsByCallableId; this.intrinsicIdTable = intrinsicIdTable; + this.localSlotByNameId = localSlotByNameId == null ? Map.of() : localSlotByNameId; } private String moduleKey() { @@ -1095,6 +1207,10 @@ public final class PbsFrontendCompiler { return intrinsicIdTable; } + private Map localSlotByNameId() { + return localSlotByNameId; + } + private ArrayList instructions() { return instructions; } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/resources/golden/frontend-irbackend.txt b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/resources/golden/frontend-irbackend.txt index 02b0e401..a43f9cb2 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/resources/golden/frontend-irbackend.txt +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/resources/golden/frontend-irbackend.txt @@ -3,7 +3,9 @@ callables=2 1:::a/0|()->simple:int intrinsics=0 fn#0:::b id=0 + GET_LOCAL calleeId=- intrinsicId=- RET calleeId=- intrinsicId=- fn#1:::a id=1 + PUSH_I32 calleeId=- intrinsicId=- CALL_FUNC calleeId=0 intrinsicId=- RET calleeId=- intrinsicId=- diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeEmitter.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeEmitter.java index 27277e04..15a158fe 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeEmitter.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeEmitter.java @@ -17,6 +17,9 @@ public class BytecodeEmitter { private static final int OP_JMP = 0x02; private static final int OP_JMP_IF_FALSE = 0x03; private static final int OP_JMP_IF_TRUE = 0x04; + private static final int OP_PUSH_CONST = 0x10; + private static final int OP_GET_LOCAL = 0x42; + private static final int OP_PUSH_I32 = 0x17; private static final int OP_HOSTCALL = 0x71; private static final int OP_SYSCALL = 0x70; private static final int OP_INTRINSIC = 0x72; @@ -48,6 +51,18 @@ public class BytecodeEmitter { writeOpU32(code, OP_CALL, op.immediate()); spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span()))); } + case PUSH_CONST -> { + writeOpU32(code, OP_PUSH_CONST, op.immediate()); + spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span()))); + } + case GET_LOCAL -> { + writeOpU32(code, OP_GET_LOCAL, op.immediate()); + spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span()))); + } + case PUSH_I32 -> { + writeOpU32(code, OP_PUSH_I32, op.immediate()); + spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span()))); + } case JMP -> { writeOpU32(code, OP_JMP, op.immediate()); spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span()))); @@ -194,6 +209,9 @@ public class BytecodeEmitter { public enum OperationKind { HALT, RET, + PUSH_CONST, + GET_LOCAL, + PUSH_I32, CALL_FUNC, JMP, JMP_IF_TRUE, @@ -254,6 +272,18 @@ public class BytecodeEmitter { return new Operation(OperationKind.CALL_FUNC, funcId, null, null, null, span); } + public static Operation pushConst(final int constPoolIndex, final BytecodeModule.SourceSpan span) { + return new Operation(OperationKind.PUSH_CONST, constPoolIndex, null, null, null, span); + } + + public static Operation getLocal(final int slot, final BytecodeModule.SourceSpan span) { + return new Operation(OperationKind.GET_LOCAL, slot, null, null, null, span); + } + + public static Operation pushI32(final int value, final BytecodeModule.SourceSpan span) { + return new Operation(OperationKind.PUSH_I32, value, null, null, null, span); + } + public static Operation hostcall( final BytecodeModule.SyscallDecl decl, final Integer expectedArgSlots, diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMOp.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMOp.java index 226ae728..4c95747d 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMOp.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMOp.java @@ -12,6 +12,7 @@ public record IRVMOp( public static final IRVMOp HALT = new IRVMOp("HALT", 0x01, 0, 0, 0, false, true, false); public static final IRVMOp RET = new IRVMOp("RET", 0x51, 0, 0, 0, false, true, false); + public static final IRVMOp PUSH_CONST = new IRVMOp("PUSH_CONST", 0x10, 4, 0, 1, false, false, false); public static final IRVMOp CALL = new IRVMOp("CALL", 0x50, 4, 0, 0, false, false, false); public static final IRVMOp JMP = new IRVMOp("JMP", 0x02, 4, 0, 0, true, true, false); public static final IRVMOp JMP_IF_TRUE = new IRVMOp("JMP_IF_TRUE", 0x04, 4, 1, 0, true, false, false); @@ -19,6 +20,6 @@ public record IRVMOp( public static final IRVMOp HOSTCALL = new IRVMOp("HOSTCALL", 0x71, 4, 0, 0, false, false, false); public static final IRVMOp INTRINSIC = new IRVMOp("INTRINSIC", 0x72, 4, 0, 0, false, false, false); public static final IRVMOp PUSH_I32 = new IRVMOp("PUSH_I32", 0x17, 4, 0, 1, false, false, false); + public static final IRVMOp GET_LOCAL = new IRVMOp("GET_LOCAL", 0x42, 4, 0, 1, false, false, false); public static final IRVMOp INTERNAL_EXT = new IRVMOp("IRVM_EXT_NOP", 0xFFFF, 0, 0, 0, false, false, true); } - diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProfileFeatureGate.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProfileFeatureGate.java index 01e8ddf7..1f2f3d6c 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProfileFeatureGate.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProfileFeatureGate.java @@ -11,6 +11,8 @@ public final class IRVMProfileFeatureGate { "core-v1", Set.of( IRVMOp.HALT, IRVMOp.RET, + IRVMOp.PUSH_CONST, + IRVMOp.GET_LOCAL, IRVMOp.CALL, IRVMOp.JMP, IRVMOp.JMP_IF_TRUE, @@ -21,6 +23,8 @@ public final class IRVMProfileFeatureGate { "experimental-v1", Set.of( IRVMOp.HALT, IRVMOp.RET, + IRVMOp.PUSH_CONST, + IRVMOp.GET_LOCAL, IRVMOp.CALL, IRVMOp.JMP, IRVMOp.JMP_IF_TRUE, diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProgram.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProgram.java index 600a1146..fb53c3cd 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProgram.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProgram.java @@ -92,6 +92,9 @@ public record IRVMProgram( return switch (instruction.op().opcode()) { case 0x01 -> operation.kind() == BytecodeEmitter.OperationKind.HALT; case 0x51 -> operation.kind() == BytecodeEmitter.OperationKind.RET; + case 0x10 -> operation.kind() == BytecodeEmitter.OperationKind.PUSH_CONST && operation.immediate() == immediate; + case 0x42 -> operation.kind() == BytecodeEmitter.OperationKind.GET_LOCAL && operation.immediate() == immediate; + case 0x17 -> operation.kind() == BytecodeEmitter.OperationKind.PUSH_I32 && operation.immediate() == immediate; case 0x50 -> operation.kind() == BytecodeEmitter.OperationKind.CALL_FUNC && operation.immediate() == immediate; case 0x02 -> operation.kind() == BytecodeEmitter.OperationKind.JMP && operation.immediate() == immediate; case 0x04 -> operation.kind() == BytecodeEmitter.OperationKind.JMP_IF_TRUE && operation.immediate() == immediate; @@ -153,6 +156,9 @@ public record IRVMProgram( return switch (instruction.op().opcode()) { case 0x01 -> BytecodeEmitter.Operation.halt(); case 0x51 -> BytecodeEmitter.Operation.ret(); + case 0x10 -> BytecodeEmitter.Operation.pushConst(immediate, null); + case 0x42 -> BytecodeEmitter.Operation.getLocal(immediate, null); + case 0x17 -> BytecodeEmitter.Operation.pushI32(immediate, null); case 0x50 -> BytecodeEmitter.Operation.callFunc(immediate); case 0x02 -> BytecodeEmitter.Operation.jmp(immediate, null); case 0x04 -> BytecodeEmitter.Operation.jmpIfTrue(immediate, null); @@ -204,6 +210,9 @@ public record IRVMProgram( return switch (operation.kind()) { case HALT -> new IRVMInstruction(IRVMOp.HALT, null); case RET -> new IRVMInstruction(IRVMOp.RET, null); + case PUSH_CONST -> new IRVMInstruction(IRVMOp.PUSH_CONST, operation.immediate()); + case GET_LOCAL -> new IRVMInstruction(IRVMOp.GET_LOCAL, operation.immediate()); + case PUSH_I32 -> new IRVMInstruction(IRVMOp.PUSH_I32, operation.immediate()); case CALL_FUNC -> new IRVMInstruction(IRVMOp.CALL, operation.immediate()); case JMP -> new IRVMInstruction(IRVMOp.JMP, operation.immediate()); case JMP_IF_TRUE -> new IRVMInstruction(IRVMOp.JMP_IF_TRUE, operation.immediate()); diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java index ec067032..39ce8235 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java @@ -56,6 +56,8 @@ public class LowerToIRVMService { final var loweredFunctions = new ArrayList(ordered.size()); final var emissionFunctions = new ArrayList(ordered.size()); + final var constPool = new ArrayList(); + final var constPoolIndexByString = new HashMap(); for (var i = 0; i < ordered.size(); i++) { final var fn = ordered.get(i); @@ -67,6 +69,44 @@ public class LowerToIRVMService { for (final var instr : fn.instructions()) { final var sourceSpan = toBytecodeSpan(fn.fileId().getId(), instr.span()); switch (instr.kind()) { + case PUSH_I32 -> { + final int value; + try { + value = Integer.parseInt(instr.label()); + } catch (NumberFormatException e) { + throw loweringError( + fn, + instr, + IRVMLoweringErrorCode.LOWER_IRVM_INVALID_INTRINSIC_ID, + "invalid PUSH_I32 literal payload: " + instr.label()); + } + instructions.add(new IRVMInstruction(IRVMOp.PUSH_I32, value)); + operations.add(BytecodeEmitter.Operation.pushI32(value, sourceSpan)); + functionPc += IRVMOp.PUSH_I32.immediateSize() + 2; + } + case PUSH_CONST -> { + final var value = instr.label() == null ? "" : instr.label(); + final var constPoolIndex = constPoolIndexByString.computeIfAbsent(value, ignored -> { + constPool.add(new BytecodeModule.StringConstant(value)); + return constPool.size() - 1; + }); + instructions.add(new IRVMInstruction(IRVMOp.PUSH_CONST, constPoolIndex)); + operations.add(BytecodeEmitter.Operation.pushConst(constPoolIndex, sourceSpan)); + functionPc += IRVMOp.PUSH_CONST.immediateSize() + 2; + } + case GET_LOCAL -> { + final var slot = instr.expectedArgSlots(); + if (slot == null) { + throw loweringError( + fn, + instr, + IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE, + "missing local slot for GET_LOCAL"); + } + instructions.add(new IRVMInstruction(IRVMOp.GET_LOCAL, slot)); + operations.add(BytecodeEmitter.Operation.getLocal(slot, sourceSpan)); + functionPc += IRVMOp.GET_LOCAL.immediateSize() + 2; + } case HALT -> { instructions.add(new IRVMInstruction(IRVMOp.HALT, null)); operations.add(BytecodeEmitter.Operation.halt(sourceSpan)); @@ -246,7 +286,7 @@ public class LowerToIRVMService { new IRVMModule(vmProfile, ReadOnlyList.wrap(loweredFunctions)), new BytecodeEmitter.EmissionPlan( 0, - ReadOnlyList.empty(), + ReadOnlyList.wrap(constPool), ReadOnlyList.empty(), ReadOnlyList.wrap(emissionFunctions))); validator.validate(program, false); diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/integration/MainProjectPipelineIntegrationTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/integration/MainProjectPipelineIntegrationTest.java new file mode 100644 index 00000000..cff539ec --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/integration/MainProjectPipelineIntegrationTest.java @@ -0,0 +1,53 @@ +package p.studio.compiler.integration; + +import org.junit.jupiter.api.Test; +import p.studio.compiler.messages.BuilderPipelineConfig; +import p.studio.compiler.workspaces.BuilderPipelineService; +import p.studio.utilities.logs.LogAggregator; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +class MainProjectPipelineIntegrationTest { + + @Test + void shouldCompileMainProjectAndWriteProgramBytecode() throws IOException { + final var repoRoot = findRepoRoot(Path.of("").toAbsolutePath().normalize()); + final var projectRoot = repoRoot.resolve("test-projects").resolve("main"); + final var outputPath = projectRoot.resolve("build").resolve("program.pbx"); + Files.createDirectories(outputPath.getParent()); + Files.deleteIfExists(outputPath); + + final var logsOut = new StringBuilder(); + final var logs = LogAggregator.with(line -> { + logsOut.append(line); + if (!line.endsWith("\n")) { + logsOut.append('\n'); + } + }); + + assertDoesNotThrow( + () -> BuilderPipelineService.INSTANCE.run(new BuilderPipelineConfig(false, projectRoot.toString()), logs), + logsOut::toString); + assertTrue(Files.exists(outputPath), "pipeline did not write output: " + outputPath + "\n" + logsOut); + assertTrue(Files.size(outputPath) > 0, "pipeline wrote empty bytecode file: " + outputPath + "\n" + logsOut); + } + + private Path findRepoRoot(final Path start) { + var current = start; + while (current != null) { + if (Files.exists(current.resolve("settings.gradle.kts")) + && Files.exists(current.resolve("test-projects").resolve("main").resolve("prometeu.json"))) { + return current; + } + current = current.getParent(); + } + fail("unable to locate repository root from " + start); + return start; + } +} diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendExecutableFunction.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendExecutableFunction.java index 4ff81345..ce4d3388 100644 --- a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendExecutableFunction.java +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRBackendExecutableFunction.java @@ -128,6 +128,45 @@ public record IRBackendExecutableFunction( throw new IllegalArgumentException("expectedRetSlots must be non-negative"); } switch (kind) { + case PUSH_I32 -> { + if (label.isBlank()) { + throw new IllegalArgumentException("PUSH_I32 requires immediate label payload"); + } + if (!targetLabel.isBlank() + || !calleeCallableName.isBlank() + || calleeCallableId != null + || hostCall != null + || intrinsicCall != null + || expectedArgSlots != null + || expectedRetSlots != null) { + throw new IllegalArgumentException("PUSH_I32 must not carry call metadata"); + } + } + case PUSH_CONST -> { + if (!targetLabel.isBlank() + || !calleeCallableName.isBlank() + || calleeCallableId != null + || hostCall != null + || intrinsicCall != null + || expectedArgSlots != null + || expectedRetSlots != null) { + throw new IllegalArgumentException("PUSH_CONST must not carry call metadata"); + } + } + case GET_LOCAL -> { + if (expectedArgSlots == null) { + throw new IllegalArgumentException("GET_LOCAL requires local slot in expectedArgSlots"); + } + if (!label.isBlank() + || !targetLabel.isBlank() + || !calleeCallableName.isBlank() + || calleeCallableId != null + || hostCall != null + || intrinsicCall != null + || expectedRetSlots != null) { + throw new IllegalArgumentException("GET_LOCAL must not carry call metadata"); + } + } case CALL_FUNC -> { if (calleeCallableId == null) { throw new IllegalArgumentException("CALL_FUNC requires calleeCallableId"); @@ -199,6 +238,9 @@ public record IRBackendExecutableFunction( } public enum InstructionKind { + PUSH_I32, + PUSH_CONST, + GET_LOCAL, HALT, RET, CALL_FUNC, diff --git a/test-projects/main/cartridge/program.pbx b/test-projects/main/cartridge/program.pbx index 9788e6fd..7b0d0589 100644 Binary files a/test-projects/main/cartridge/program.pbx and b/test-projects/main/cartridge/program.pbx differ