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 new file mode 100644 index 00000000..a812e471 --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProfileFeatureGate.java @@ -0,0 +1,44 @@ +package p.studio.compiler.backend.irvm; + +import java.util.Map; +import java.util.Set; + +public final class IRVMProfileFeatureGate { + private final Map> allowedByProfile; + + public IRVMProfileFeatureGate() { + this(Map.of( + "core-v1", Set.of( + IRVMOp.HALT, + IRVMOp.RET, + IRVMOp.CALL, + IRVMOp.JMP, + IRVMOp.JMP_IF_TRUE, + IRVMOp.JMP_IF_FALSE, + IRVMOp.HOSTCALL, + IRVMOp.INTRINSIC, + IRVMOp.PUSH_I32), + "experimental-v1", Set.of( + IRVMOp.HALT, + IRVMOp.RET, + IRVMOp.CALL, + IRVMOp.JMP, + IRVMOp.JMP_IF_TRUE, + IRVMOp.JMP_IF_FALSE, + IRVMOp.HOSTCALL, + IRVMOp.INTRINSIC, + IRVMOp.PUSH_I32, + IRVMOp.INTERNAL_EXT))); + } + + IRVMProfileFeatureGate(final Map> allowedByProfile) { + this.allowedByProfile = allowedByProfile == null ? Map.of() : Map.copyOf(allowedByProfile); + } + + public boolean isAllowed( + final String profile, + final IRVMOp op) { + final var allowed = allowedByProfile.get(profile == null || profile.isBlank() ? "core-v1" : profile); + return allowed != null && allowed.contains(op); + } +} 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 index 10b67a24..068c33b8 100644 --- 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 @@ -9,5 +9,5 @@ public enum IRVMValidationErrorCode { MARSHAL_VERIFY_PRECHECK_INVALID_FUNC_ID, MARSHAL_VERIFY_PRECHECK_UNTERMINATED_PATH, MARSHAL_VERIFY_PRECHECK_INTERNAL_OPCODE_RESIDUAL, + MARSHAL_VERIFY_PRECHECK_OPCODE_NOT_ALLOWED_FOR_PROFILE, } - 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 index 383c299c..f49fa871 100644 --- 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 @@ -5,6 +5,15 @@ import java.util.HashMap; import java.util.HashSet; public class IRVMValidator { + private final IRVMProfileFeatureGate profileFeatureGate; + + public IRVMValidator() { + this(new IRVMProfileFeatureGate()); + } + + IRVMValidator(final IRVMProfileFeatureGate profileFeatureGate) { + this.profileFeatureGate = profileFeatureGate; + } public void validate( final IRVMModule module, @@ -40,6 +49,13 @@ public class IRVMValidator { functionIndex, pcByIndex[i]); } + if (!profileFeatureGate.isAllowed(module.vmProfile(), instr.op())) { + throw new IRVMValidationException( + IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_OPCODE_NOT_ALLOWED_FOR_PROFILE, + "opcode '%s' is not allowed for vm profile '%s'".formatted(instr.op().name(), module.vmProfile()), + functionIndex, + pcByIndex[i]); + } } final var worklist = new ArrayDeque(); @@ -222,4 +238,3 @@ public class IRVMValidator { } } } - 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 index 26e0b7ff..fa8bc0f3 100644 --- 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 @@ -4,6 +4,7 @@ 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.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; class IRVMValidatorTest { @@ -84,5 +85,41 @@ class IRVMValidatorTest { final var thrown = assertThrows(IRVMValidationException.class, () -> validator.validate(module, true)); assertEquals(IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_INTERNAL_OPCODE_RESIDUAL, thrown.code()); } -} + @Test + void validateMustRejectOpcodeOutsideProfileFeatureGate() { + 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, false)); + assertEquals(IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_OPCODE_NOT_ALLOWED_FOR_PROFILE, thrown.code()); + } + + @Test + void validateMayAllowProfileSpecificOpcodeWhenProfileSupportsIt() { + final var module = new IRVMModule( + "experimental-v1", + ReadOnlyList.from( + new IRVMFunction( + "main", + 0, + 0, + 0, + 1, + ReadOnlyList.from( + new IRVMInstruction(IRVMOp.INTERNAL_EXT, null), + new IRVMInstruction(IRVMOp.HALT, null))))); + + assertDoesNotThrow(() -> validator.validate(module, false)); + } +}