From e6bd796f4fa8a4e4c0f151b254f586933a8cc6d9 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Sat, 7 Mar 2026 16:33:51 +0000 Subject: [PATCH] implements PR-036 --- .../compiler/backend/irvm/IRVMFunction.java | 19 ++ .../backend/irvm/IRVMInstruction.java | 22 ++ .../compiler/backend/irvm/IRVMModule.java | 17 ++ .../studio/compiler/backend/irvm/IRVMOp.java | 24 ++ .../backend/irvm/IRVMValidationErrorCode.java | 13 + .../backend/irvm/IRVMValidationException.java | 31 +++ .../compiler/backend/irvm/IRVMValidator.java | 225 ++++++++++++++++++ .../backend/irvm/IRVMValidatorTest.java | 88 +++++++ 8 files changed, 439 insertions(+) create mode 100644 prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMFunction.java create mode 100644 prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMInstruction.java create mode 100644 prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMModule.java create mode 100644 prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMOp.java create mode 100644 prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMValidationErrorCode.java create mode 100644 prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMValidationException.java create mode 100644 prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMValidator.java create mode 100644 prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/IRVMValidatorTest.java diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMFunction.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMFunction.java new file mode 100644 index 00000000..3f0a7397 --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMFunction.java @@ -0,0 +1,19 @@ +package p.studio.compiler.backend.irvm; + +import p.studio.utilities.structures.ReadOnlyList; + +import java.util.Objects; + +public record IRVMFunction( + String name, + int paramSlots, + int localSlots, + int returnSlots, + int maxStackSlots, + ReadOnlyList instructions) { + public IRVMFunction { + Objects.requireNonNull(name, "name"); + instructions = instructions == null ? ReadOnlyList.empty() : instructions; + } +} + diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMInstruction.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMInstruction.java new file mode 100644 index 00000000..4d0a136e --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMInstruction.java @@ -0,0 +1,22 @@ +package p.studio.compiler.backend.irvm; + +import java.util.Objects; + +public record IRVMInstruction( + IRVMOp op, + Integer immediate) { + public IRVMInstruction { + Objects.requireNonNull(op, "op"); + if (op.immediateSize() > 0 && immediate == null) { + throw new IllegalArgumentException("immediate is required for opcode: " + op.name()); + } + if (op.immediateSize() == 0 && immediate != null) { + throw new IllegalArgumentException("immediate is not allowed for opcode: " + op.name()); + } + } + + public int encodedSize() { + return 2 + op.immediateSize(); + } +} + diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMModule.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMModule.java new file mode 100644 index 00000000..02b2da79 --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMModule.java @@ -0,0 +1,17 @@ +package p.studio.compiler.backend.irvm; + +import p.studio.utilities.structures.ReadOnlyList; + +public record IRVMModule( + String vmProfile, + ReadOnlyList functions) { + public IRVMModule { + vmProfile = vmProfile == null || vmProfile.isBlank() ? "core-v1" : vmProfile; + functions = functions == null ? ReadOnlyList.empty() : functions; + } + + public static IRVMModule empty() { + return new IRVMModule("core-v1", ReadOnlyList.empty()); + } +} + 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 new file mode 100644 index 00000000..226ae728 --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMOp.java @@ -0,0 +1,24 @@ +package p.studio.compiler.backend.irvm; + +public record IRVMOp( + String name, + int opcode, + int immediateSize, + int pops, + int pushes, + boolean branch, + boolean terminator, + boolean internal) { + + 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 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); + public static final IRVMOp JMP_IF_FALSE = new IRVMOp("JMP_IF_FALSE", 0x03, 4, 1, 0, true, false, false); + 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 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/IRVMValidationErrorCode.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMValidationErrorCode.java new file mode 100644 index 00000000..10b67a24 --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMValidationErrorCode.java @@ -0,0 +1,13 @@ +package p.studio.compiler.backend.irvm; + +public enum IRVMValidationErrorCode { + MARSHAL_VERIFY_PRECHECK_INVALID_JUMP_TARGET, + MARSHAL_VERIFY_PRECHECK_STACK_UNDERFLOW, + MARSHAL_VERIFY_PRECHECK_STACK_OVERFLOW, + MARSHAL_VERIFY_PRECHECK_STACK_MISMATCH_JOIN, + MARSHAL_VERIFY_PRECHECK_BAD_RET_STACK_HEIGHT, + MARSHAL_VERIFY_PRECHECK_INVALID_FUNC_ID, + MARSHAL_VERIFY_PRECHECK_UNTERMINATED_PATH, + MARSHAL_VERIFY_PRECHECK_INTERNAL_OPCODE_RESIDUAL, +} + diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMValidationException.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMValidationException.java new file mode 100644 index 00000000..46825f9a --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMValidationException.java @@ -0,0 +1,31 @@ +package p.studio.compiler.backend.irvm; + +public class IRVMValidationException extends RuntimeException { + private final IRVMValidationErrorCode code; + private final int functionIndex; + private final int pc; + + public IRVMValidationException( + final IRVMValidationErrorCode code, + final String message, + final int functionIndex, + final int pc) { + super(message); + this.code = code; + this.functionIndex = functionIndex; + this.pc = pc; + } + + public IRVMValidationErrorCode code() { + return code; + } + + public int functionIndex() { + return functionIndex; + } + + public int pc() { + return pc; + } +} + diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMValidator.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMValidator.java new file mode 100644 index 00000000..383c299c --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMValidator.java @@ -0,0 +1,225 @@ +package p.studio.compiler.backend.irvm; + +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.HashSet; + +public class IRVMValidator { + + public void validate( + final IRVMModule module, + final boolean rejectInternalOpcodes) { + final var input = module == null ? IRVMModule.empty() : module; + for (var functionIndex = 0; functionIndex < input.functions().size(); functionIndex++) { + validateFunction(input, functionIndex, rejectInternalOpcodes); + } + } + + private void validateFunction( + final IRVMModule module, + final int functionIndex, + final boolean rejectInternalOpcodes) { + final var function = module.functions().get(functionIndex); + final var instructions = function.instructions(); + if (instructions.isEmpty()) { + return; + } + + final var pcByIndex = new int[instructions.size()]; + final var indexByPc = new HashMap(); + var pc = 0; + for (var i = 0; i < instructions.size(); i++) { + final var instr = instructions.get(i); + pcByIndex[i] = pc; + indexByPc.put(pc, i); + pc += instr.encodedSize(); + if (rejectInternalOpcodes && instr.op().internal()) { + throw new IRVMValidationException( + IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_INTERNAL_OPCODE_RESIDUAL, + "internal opcode must be eliminated before emission", + functionIndex, + pcByIndex[i]); + } + } + + final var worklist = new ArrayDeque(); + final var incomingHeights = new HashMap(); + worklist.add(0); + incomingHeights.put(0, 0); + final var visited = new HashSet(); + + while (!worklist.isEmpty()) { + final var index = worklist.removeFirst(); + visited.add(index); + final var instr = instructions.get(index); + final var instrPc = pcByIndex[index]; + var inHeight = incomingHeights.get(index); + + if (instr.op() == IRVMOp.CALL) { + final var targetFn = instr.immediate(); + if (targetFn == null || targetFn < 0 || targetFn >= module.functions().size()) { + throw new IRVMValidationException( + IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_INVALID_FUNC_ID, + "invalid callee function id: " + targetFn, + functionIndex, + instrPc); + } + final var callee = module.functions().get(targetFn); + final var outHeight = applyStackEffect( + inHeight, + callee.paramSlots(), + callee.returnSlots(), + function.maxStackSlots(), + functionIndex, + instrPc); + enqueueSuccessors( + module, + functionIndex, + function, + index, + instr, + instrPc, + pcByIndex, + indexByPc, + outHeight, + incomingHeights, + worklist); + continue; + } + + final var outHeight = applyStackEffect( + inHeight, + instr.op().pops(), + instr.op().pushes(), + function.maxStackSlots(), + functionIndex, + instrPc); + if (instr.op() == IRVMOp.RET && outHeight != function.returnSlots()) { + throw new IRVMValidationException( + IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_BAD_RET_STACK_HEIGHT, + "ret stack height mismatch. expected=%d actual=%d".formatted(function.returnSlots(), outHeight), + functionIndex, + instrPc); + } + enqueueSuccessors( + module, + functionIndex, + function, + index, + instr, + instrPc, + pcByIndex, + indexByPc, + outHeight, + incomingHeights, + worklist); + } + + if (visited.isEmpty()) { + return; + } + } + + private int applyStackEffect( + final int inHeight, + final int pops, + final int pushes, + final int maxStackSlots, + final int functionIndex, + final int pc) { + if (inHeight < pops) { + throw new IRVMValidationException( + IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_STACK_UNDERFLOW, + "stack underflow", + functionIndex, + pc); + } + final var outHeight = inHeight - pops + pushes; + if (outHeight > maxStackSlots) { + throw new IRVMValidationException( + IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_STACK_OVERFLOW, + "stack overflow. max=" + maxStackSlots + " actual=" + outHeight, + functionIndex, + pc); + } + return outHeight; + } + + private void enqueueSuccessors( + final IRVMModule module, + final int functionIndex, + final IRVMFunction function, + final int index, + final IRVMInstruction instr, + final int instrPc, + final int[] pcByIndex, + final HashMap indexByPc, + final int outHeight, + final HashMap incomingHeights, + final ArrayDeque worklist) { + if (instr.op() == IRVMOp.HALT || instr.op() == IRVMOp.RET) { + return; + } + + if (instr.op() == IRVMOp.JMP || instr.op() == IRVMOp.JMP_IF_TRUE || instr.op() == IRVMOp.JMP_IF_FALSE) { + final var targetPc = instr.immediate(); + final var targetIndex = indexByPc.get(targetPc); + if (targetIndex == null) { + throw new IRVMValidationException( + IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_INVALID_JUMP_TARGET, + "invalid jump target pc=" + targetPc, + functionIndex, + instrPc); + } + mergeIncomingHeight( + functionIndex, + pcByIndex[targetIndex], + targetIndex, + outHeight, + incomingHeights, + worklist); + if (instr.op() == IRVMOp.JMP) { + return; + } + } + + final var nextIndex = index + 1; + if (nextIndex >= function.instructions().size()) { + throw new IRVMValidationException( + IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_UNTERMINATED_PATH, + "reachable path ends without terminator", + functionIndex, + instrPc); + } + mergeIncomingHeight( + functionIndex, + pcByIndex[nextIndex], + nextIndex, + outHeight, + incomingHeights, + worklist); + } + + private void mergeIncomingHeight( + final int functionIndex, + final int pc, + final int index, + final int newHeight, + final HashMap incomingHeights, + final ArrayDeque worklist) { + final var existing = incomingHeights.get(index); + if (existing == null) { + incomingHeights.put(index, newHeight); + worklist.add(index); + return; + } + if (existing != newHeight) { + throw new IRVMValidationException( + IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_STACK_MISMATCH_JOIN, + "stack height mismatch at join. existing=%d new=%d".formatted(existing, newHeight), + functionIndex, + pc); + } + } +} + diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/IRVMValidatorTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/IRVMValidatorTest.java new file mode 100644 index 00000000..26e0b7ff --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/IRVMValidatorTest.java @@ -0,0 +1,88 @@ +package p.studio.compiler.backend.irvm; + +import org.junit.jupiter.api.Test; +import p.studio.utilities.structures.ReadOnlyList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class IRVMValidatorTest { + private final IRVMValidator validator = new IRVMValidator(); + + @Test + void validateMustRejectInvalidJumpTarget() { + final var module = new IRVMModule( + "core-v1", + ReadOnlyList.from( + new IRVMFunction( + "main", + 0, + 0, + 0, + 1, + ReadOnlyList.from( + new IRVMInstruction(IRVMOp.JMP, 999))))); + + final var thrown = assertThrows(IRVMValidationException.class, () -> validator.validate(module, false)); + assertEquals(IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_INVALID_JUMP_TARGET, thrown.code()); + } + + @Test + void validateMustRejectStackMismatchJoin() { + final var module = new IRVMModule( + "core-v1", + ReadOnlyList.from( + new IRVMFunction( + "main", + 0, + 0, + 0, + 2, + ReadOnlyList.from( + new IRVMInstruction(IRVMOp.PUSH_I32, 1), + new IRVMInstruction(IRVMOp.JMP_IF_TRUE, 18), + new IRVMInstruction(IRVMOp.PUSH_I32, 2), + new IRVMInstruction(IRVMOp.RET, null))))); + + final var thrown = assertThrows(IRVMValidationException.class, () -> validator.validate(module, false)); + assertEquals(IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_STACK_MISMATCH_JOIN, thrown.code()); + } + + @Test + void validateMustRejectRetShapeMismatch() { + final var module = new IRVMModule( + "core-v1", + ReadOnlyList.from( + new IRVMFunction( + "main", + 0, + 0, + 1, + 1, + ReadOnlyList.from( + new IRVMInstruction(IRVMOp.RET, null))))); + + final var thrown = assertThrows(IRVMValidationException.class, () -> validator.validate(module, false)); + assertEquals(IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_BAD_RET_STACK_HEIGHT, thrown.code()); + } + + @Test + void validateMustRejectInternalOpcodeWhenConfigured() { + final var module = new IRVMModule( + "core-v1", + ReadOnlyList.from( + new IRVMFunction( + "main", + 0, + 0, + 0, + 1, + ReadOnlyList.from( + new IRVMInstruction(IRVMOp.INTERNAL_EXT, null), + new IRVMInstruction(IRVMOp.HALT, null))))); + + final var thrown = assertThrows(IRVMValidationException.class, () -> validator.validate(module, true)); + assertEquals(IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_INTERNAL_OPCODE_RESIDUAL, thrown.code()); + } +} +