implements PR-05.5

This commit is contained in:
bQUARKz 2026-03-09 07:06:43 +00:00
parent 6a7cd6a40a
commit 48ce448203
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
12 changed files with 196 additions and 13 deletions

View File

@ -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;
}

View File

@ -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()));
}

View File

@ -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);
}

View File

@ -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,

View File

@ -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) {
}
}

View File

@ -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<IRBackendExecutableFunction> orderFunctions(

View File

@ -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;
}

View File

@ -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,

View File

@ -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());
}
}

View File

@ -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());
}

View File

@ -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());
}

View File

@ -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");
}
}