From 48ce448203e7158ec915de0f0f025f7419692d25 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 9 Mar 2026 07:06:43 +0000 Subject: [PATCH] implements PR-05.5 --- .../compiler/pbs/PbsFrontendCompiler.java | 2 + .../PbsReservedMetadataExtractor.java | 5 ++ .../backend/bytecode/BytecodeEmitter.java | 8 ++ .../backend/irvm/IRVMValidationErrorCode.java | 2 + .../compiler/backend/irvm/IRVMValidator.java | 88 ++++++++++++++++++- .../backend/irvm/LowerToIRVMService.java | 14 +-- .../backend/irvm/OptimizeIRVMService.java | 4 +- .../compiler/backend/GoldenArtifactsTest.java | 2 + .../backend/irvm/IRVMValidatorTest.java | 69 +++++++++++++++ .../backend/irvm/LowerToIRVMServiceTest.java | 4 +- .../BackendGateIIntegrationTest.java | 6 +- .../compiler/models/IRReservedMetadata.java | 5 ++ 12 files changed, 196 insertions(+), 13 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 7bce22e4..71195bb5 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 @@ -660,6 +660,8 @@ public final class PbsFrontendCompiler { intrinsic.canonicalName(), intrinsic.canonicalVersion(), context.intrinsicIdTable().register(intrinsic.canonicalName(), intrinsic.canonicalVersion())), + intrinsic.argSlots(), + intrinsic.retSlots(), callExpr.span())); return; } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/metadata/PbsReservedMetadataExtractor.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/metadata/PbsReservedMetadataExtractor.java index 0771a6cc..45f52317 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/metadata/PbsReservedMetadataExtractor.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/metadata/PbsReservedMetadataExtractor.java @@ -123,6 +123,11 @@ public final class PbsReservedMetadataExtractor { signature.name(), stringArgument(intrinsicMetadata, "name").orElse(signature.name()), longArgument(intrinsicMetadata, "version").orElse(canonicalVersion), + signature.parameters().size(), + switch (signature.returnKind()) { + case INFERRED_UNIT, EXPLICIT_UNIT -> 0; + case PLAIN, RESULT -> 1; + }, signature.span())); } 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 76fc7964..27277e04 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 @@ -238,6 +238,14 @@ public class BytecodeEmitter { return new Operation(OperationKind.INTRINSIC, intrinsicId, null, null, null, span); } + public static Operation intrinsic( + final int intrinsicId, + final Integer expectedArgSlots, + final Integer expectedRetSlots, + final BytecodeModule.SourceSpan span) { + return new Operation(OperationKind.INTRINSIC, intrinsicId, null, expectedArgSlots, expectedRetSlots, span); + } + public static Operation callFunc(final int funcId) { return callFunc(funcId, null); } 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 068c33b8..1dcdb7f8 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 @@ -7,6 +7,8 @@ public enum IRVMValidationErrorCode { MARSHAL_VERIFY_PRECHECK_STACK_MISMATCH_JOIN, MARSHAL_VERIFY_PRECHECK_BAD_RET_STACK_HEIGHT, MARSHAL_VERIFY_PRECHECK_INVALID_FUNC_ID, + MARSHAL_VERIFY_PRECHECK_HOSTCALL_METADATA_MISSING, + MARSHAL_VERIFY_PRECHECK_INTRINSIC_SIGNATURE_MISSING, 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 f49fa871..2191c201 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 @@ -1,5 +1,7 @@ package p.studio.compiler.backend.irvm; +import p.studio.compiler.backend.bytecode.BytecodeEmitter; + import java.util.ArrayDeque; import java.util.HashMap; import java.util.HashSet; @@ -15,17 +17,36 @@ public class IRVMValidator { this.profileFeatureGate = profileFeatureGate; } + public void validate( + final IRVMProgram program, + final boolean rejectInternalOpcodes) { + final var input = program == null ? IRVMProgram.empty() : program; + final BytecodeEmitter.EmissionPlan emissionPlan; + if (input.emissionPlan() == null || input.emissionPlan().functions().isEmpty()) { + emissionPlan = null; + } else { + emissionPlan = input.coherentEmissionPlan(); + } + for (var functionIndex = 0; functionIndex < input.module().functions().size(); functionIndex++) { + final var functionPlan = emissionPlan == null + ? null + : emissionPlan.functions().get(functionIndex); + validateFunction(input.module(), functionPlan, functionIndex, rejectInternalOpcodes); + } + } + 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); + validateFunction(input, null, functionIndex, rejectInternalOpcodes); } } private void validateFunction( final IRVMModule module, + final BytecodeEmitter.FunctionPlan functionPlan, final int functionIndex, final boolean rejectInternalOpcodes) { final var function = module.functions().get(functionIndex); @@ -103,10 +124,16 @@ public class IRVMValidator { continue; } + final var stackEffect = resolveStackEffect( + functionPlan, + index, + instr, + functionIndex, + instrPc); final var outHeight = applyStackEffect( inHeight, - instr.op().pops(), - instr.op().pushes(), + stackEffect.pops(), + stackEffect.pushes(), function.maxStackSlots(), functionIndex, instrPc); @@ -237,4 +264,59 @@ public class IRVMValidator { pc); } } + + private StackEffect resolveStackEffect( + final BytecodeEmitter.FunctionPlan functionPlan, + final int instructionIndex, + final IRVMInstruction instruction, + final int functionIndex, + final int pc) { + final BytecodeEmitter.Operation operation; + if (functionPlan == null || instructionIndex >= functionPlan.operations().size()) { + operation = null; + } else { + operation = functionPlan.operations().get(instructionIndex); + } + + if (instruction.op() == IRVMOp.HOSTCALL) { + if (operation == null || operation.kind() != BytecodeEmitter.OperationKind.HOSTCALL) { + return new StackEffect(instruction.op().pops(), instruction.op().pushes()); + } + final Integer pops = operation.expectedArgSlots() != null + ? operation.expectedArgSlots() + : operation.syscallDecl() == null ? null : operation.syscallDecl().argSlots(); + final Integer pushes = operation.expectedRetSlots() != null + ? operation.expectedRetSlots() + : operation.syscallDecl() == null ? null : operation.syscallDecl().retSlots(); + if (pops == null || pushes == null) { + throw new IRVMValidationException( + IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_HOSTCALL_METADATA_MISSING, + "hostcall stack metadata is missing", + functionIndex, + pc); + } + return new StackEffect(pops, pushes); + } + + if (instruction.op() == IRVMOp.INTRINSIC) { + if (operation == null || operation.kind() != BytecodeEmitter.OperationKind.INTRINSIC) { + return new StackEffect(instruction.op().pops(), instruction.op().pushes()); + } + if (operation.expectedArgSlots() == null || operation.expectedRetSlots() == null) { + throw new IRVMValidationException( + IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_INTRINSIC_SIGNATURE_MISSING, + "intrinsic stack signature metadata is missing", + functionIndex, + pc); + } + return new StackEffect(operation.expectedArgSlots(), operation.expectedRetSlots()); + } + + return new StackEffect(instruction.op().pops(), instruction.op().pushes()); + } + + private record StackEffect( + int pops, + int pushes) { + } } 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 aacab4e9..907272dc 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 @@ -135,7 +135,11 @@ public class LowerToIRVMService { "invalid intrinsic id: " + intrinsic.intrinsicId()); } instructions.add(new IRVMInstruction(IRVMOp.INTRINSIC, intrinsicIndex)); - operations.add(BytecodeEmitter.Operation.intrinsic(intrinsicIndex, sourceSpan)); + operations.add(BytecodeEmitter.Operation.intrinsic( + intrinsicIndex, + instr.expectedArgSlots(), + instr.expectedRetSlots(), + sourceSpan)); functionPc += IRVMOp.INTRINSIC.immediateSize() + 2; } case LABEL -> { @@ -202,15 +206,15 @@ public class LowerToIRVMService { ReadOnlyList.wrap(operations))); } - final var module = new IRVMModule(vmProfile, ReadOnlyList.wrap(loweredFunctions)); - validator.validate(module, false); - return new IRVMProgram( - module, + final var program = new IRVMProgram( + new IRVMModule(vmProfile, ReadOnlyList.wrap(loweredFunctions)), new BytecodeEmitter.EmissionPlan( 0, ReadOnlyList.empty(), ReadOnlyList.empty(), ReadOnlyList.wrap(emissionFunctions))); + validator.validate(program, false); + return program; } private ReadOnlyList orderFunctions( diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/OptimizeIRVMService.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/OptimizeIRVMService.java index d4aae29d..5dca8737 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/OptimizeIRVMService.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/OptimizeIRVMService.java @@ -37,7 +37,7 @@ public class OptimizeIRVMService { throw new IllegalArgumentException("unsupported vm profile: " + program.module().vmProfile()); } var current = program; - validator.validate(current.module(), false); + validator.validate(current, false); for (final var pass : passes) { if (pass == null || !pass.enabled()) { continue; @@ -47,7 +47,7 @@ public class OptimizeIRVMService { if (!beforeProfile.equals(current.module().vmProfile())) { throw new IllegalArgumentException("pass changed vm profile: " + pass.name()); } - validator.validate(current.module(), false); + validator.validate(current, false); } return current; } diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/GoldenArtifactsTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/GoldenArtifactsTest.java index 11012404..96200a5d 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/GoldenArtifactsTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/GoldenArtifactsTest.java @@ -69,6 +69,8 @@ class GoldenArtifactsTest { "", null, new IRBackendExecutableFunction.IntrinsicCallMetadata("core.color.pack", 1, new IntrinsicId(0)), + 0, + 0, Span.none()), new IRBackendExecutableFunction.Instruction( IRBackendExecutableFunction.InstructionKind.RET, 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 fa8bc0f3..8dfc22e1 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 @@ -1,6 +1,8 @@ package p.studio.compiler.backend.irvm; import org.junit.jupiter.api.Test; +import p.studio.compiler.backend.bytecode.BytecodeEmitter; +import p.studio.compiler.backend.bytecode.BytecodeModule; import p.studio.utilities.structures.ReadOnlyList; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -122,4 +124,71 @@ class IRVMValidatorTest { assertDoesNotThrow(() -> validator.validate(module, false)); } + + @Test + void validateProgramMustRejectIntrinsicWithoutSignatureMetadata() { + final var module = new IRVMModule( + "core-v1", + ReadOnlyList.from(new IRVMFunction( + "main", + 0, + 0, + 0, + 1, + ReadOnlyList.from( + new IRVMInstruction(IRVMOp.INTRINSIC, 0), + new IRVMInstruction(IRVMOp.RET, null))))); + final var plan = new BytecodeEmitter.EmissionPlan( + 0, + ReadOnlyList.empty(), + ReadOnlyList.empty(), + ReadOnlyList.from(new BytecodeEmitter.FunctionPlan( + "main", + 0, + 0, + 0, + 1, + ReadOnlyList.from( + BytecodeEmitter.Operation.intrinsic(0, null), + BytecodeEmitter.Operation.ret())))); + final var program = new IRVMProgram(module, plan); + + final var thrown = assertThrows(IRVMValidationException.class, () -> validator.validate(program, false)); + assertEquals(IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_INTRINSIC_SIGNATURE_MISSING, thrown.code()); + } + + @Test + void validateProgramMustApplyHostcallStackEffectsFromMetadata() { + final var module = new IRVMModule( + "core-v1", + ReadOnlyList.from(new IRVMFunction( + "main", + 0, + 0, + 0, + 1, + ReadOnlyList.from( + new IRVMInstruction(IRVMOp.HOSTCALL, 0), + new IRVMInstruction(IRVMOp.RET, null))))); + final var plan = new BytecodeEmitter.EmissionPlan( + 0, + ReadOnlyList.empty(), + ReadOnlyList.empty(), + ReadOnlyList.from(new BytecodeEmitter.FunctionPlan( + "main", + 0, + 0, + 0, + 1, + ReadOnlyList.from( + BytecodeEmitter.Operation.hostcall( + new BytecodeModule.SyscallDecl("gfx", "draw_pixel", 1, 1, 0), + 1, + 0), + BytecodeEmitter.Operation.ret())))); + final var program = new IRVMProgram(module, plan); + + final var thrown = assertThrows(IRVMValidationException.class, () -> validator.validate(program, false)); + assertEquals(IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_STACK_UNDERFLOW, thrown.code()); + } } diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java index cb70343f..900be92b 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java @@ -41,7 +41,7 @@ class LowerToIRVMServiceTest { final var backend = IRBackend.builder() .executableFunctions(ReadOnlyList.from( fn("main", "app", 10, ReadOnlyList.from( - callHost("gfx", "draw_pixel", 1, 2, 0), + callHost("gfx", "draw_pixel", 1, 0, 0), callIntrinsic("core.color.pack", 1, 0), ret())))) .intrinsicPool(ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1))) @@ -240,6 +240,8 @@ class LowerToIRVMServiceTest { "", null, new IRBackendExecutableFunction.IntrinsicCallMetadata(canonicalName, canonicalVersion, new IntrinsicId(intrinsicId)), + 0, + 0, Span.none()); } diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/integration/BackendGateIIntegrationTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/integration/BackendGateIIntegrationTest.java index 8cece650..cee8ba61 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/integration/BackendGateIIntegrationTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/integration/BackendGateIIntegrationTest.java @@ -48,7 +48,7 @@ class BackendGateIIntegrationTest { void gateI_validHostcallPath() { final var module = emitFromBackend(backendWithSingleFunction( fn("main", ReadOnlyList.from( - callHost("gfx", "draw_pixel", 1, 2, 0), + callHost("gfx", "draw_pixel", 1, 0, 0), ret())))); final var check = compatibilityAdapter.check(module); @@ -131,7 +131,7 @@ class BackendGateIIntegrationTest { void gateI_rejectMissingCapability() { final var module = emitFromBackend(backendWithSingleFunction( fn("main", ReadOnlyList.from( - callHost("gfx", "draw_pixel", 1, 2, 0), + callHost("gfx", "draw_pixel", 1, 0, 0), ret())))); final var caps = Set.of("input"); @@ -236,6 +236,8 @@ class BackendGateIIntegrationTest { "", null, new IRBackendExecutableFunction.IntrinsicCallMetadata(canonicalName, canonicalVersion, new IntrinsicId(intrinsicId)), + 0, + 0, Span.none()); } diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRReservedMetadata.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRReservedMetadata.java index b5ed1a5b..832deae0 100644 --- a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRReservedMetadata.java +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRReservedMetadata.java @@ -77,10 +77,15 @@ public record IRReservedMetadata( String sourceMethodName, String canonicalName, long canonicalVersion, + int argSlots, + int retSlots, Span span) { public IntrinsicSurface { sourceMethodName = Objects.requireNonNull(sourceMethodName, "sourceMethodName"); canonicalName = Objects.requireNonNull(canonicalName, "canonicalName"); + if (argSlots < 0 || retSlots < 0) { + throw new IllegalArgumentException("intrinsic slot counts must be non-negative"); + } span = Objects.requireNonNull(span, "span"); } }