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.canonicalName(),
intrinsic.canonicalVersion(), intrinsic.canonicalVersion(),
context.intrinsicIdTable().register(intrinsic.canonicalName(), intrinsic.canonicalVersion())), context.intrinsicIdTable().register(intrinsic.canonicalName(), intrinsic.canonicalVersion())),
intrinsic.argSlots(),
intrinsic.retSlots(),
callExpr.span())); callExpr.span()));
return; return;
} }

View File

@ -123,6 +123,11 @@ public final class PbsReservedMetadataExtractor {
signature.name(), signature.name(),
stringArgument(intrinsicMetadata, "name").orElse(signature.name()), stringArgument(intrinsicMetadata, "name").orElse(signature.name()),
longArgument(intrinsicMetadata, "version").orElse(canonicalVersion), longArgument(intrinsicMetadata, "version").orElse(canonicalVersion),
signature.parameters().size(),
switch (signature.returnKind()) {
case INFERRED_UNIT, EXPLICIT_UNIT -> 0;
case PLAIN, RESULT -> 1;
},
signature.span())); signature.span()));
} }

View File

@ -238,6 +238,14 @@ public class BytecodeEmitter {
return new Operation(OperationKind.INTRINSIC, intrinsicId, null, null, null, span); 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) { public static Operation callFunc(final int funcId) {
return callFunc(funcId, null); return callFunc(funcId, null);
} }

View File

@ -7,6 +7,8 @@ public enum IRVMValidationErrorCode {
MARSHAL_VERIFY_PRECHECK_STACK_MISMATCH_JOIN, MARSHAL_VERIFY_PRECHECK_STACK_MISMATCH_JOIN,
MARSHAL_VERIFY_PRECHECK_BAD_RET_STACK_HEIGHT, MARSHAL_VERIFY_PRECHECK_BAD_RET_STACK_HEIGHT,
MARSHAL_VERIFY_PRECHECK_INVALID_FUNC_ID, 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_UNTERMINATED_PATH,
MARSHAL_VERIFY_PRECHECK_INTERNAL_OPCODE_RESIDUAL, MARSHAL_VERIFY_PRECHECK_INTERNAL_OPCODE_RESIDUAL,
MARSHAL_VERIFY_PRECHECK_OPCODE_NOT_ALLOWED_FOR_PROFILE, MARSHAL_VERIFY_PRECHECK_OPCODE_NOT_ALLOWED_FOR_PROFILE,

View File

@ -1,5 +1,7 @@
package p.studio.compiler.backend.irvm; package p.studio.compiler.backend.irvm;
import p.studio.compiler.backend.bytecode.BytecodeEmitter;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -15,17 +17,36 @@ public class IRVMValidator {
this.profileFeatureGate = profileFeatureGate; 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( public void validate(
final IRVMModule module, final IRVMModule module,
final boolean rejectInternalOpcodes) { final boolean rejectInternalOpcodes) {
final var input = module == null ? IRVMModule.empty() : module; final var input = module == null ? IRVMModule.empty() : module;
for (var functionIndex = 0; functionIndex < input.functions().size(); functionIndex++) { for (var functionIndex = 0; functionIndex < input.functions().size(); functionIndex++) {
validateFunction(input, functionIndex, rejectInternalOpcodes); validateFunction(input, null, functionIndex, rejectInternalOpcodes);
} }
} }
private void validateFunction( private void validateFunction(
final IRVMModule module, final IRVMModule module,
final BytecodeEmitter.FunctionPlan functionPlan,
final int functionIndex, final int functionIndex,
final boolean rejectInternalOpcodes) { final boolean rejectInternalOpcodes) {
final var function = module.functions().get(functionIndex); final var function = module.functions().get(functionIndex);
@ -103,10 +124,16 @@ public class IRVMValidator {
continue; continue;
} }
final var stackEffect = resolveStackEffect(
functionPlan,
index,
instr,
functionIndex,
instrPc);
final var outHeight = applyStackEffect( final var outHeight = applyStackEffect(
inHeight, inHeight,
instr.op().pops(), stackEffect.pops(),
instr.op().pushes(), stackEffect.pushes(),
function.maxStackSlots(), function.maxStackSlots(),
functionIndex, functionIndex,
instrPc); instrPc);
@ -237,4 +264,59 @@ public class IRVMValidator {
pc); 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()); "invalid intrinsic id: " + intrinsic.intrinsicId());
} }
instructions.add(new IRVMInstruction(IRVMOp.INTRINSIC, intrinsicIndex)); 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; functionPc += IRVMOp.INTRINSIC.immediateSize() + 2;
} }
case LABEL -> { case LABEL -> {
@ -202,15 +206,15 @@ public class LowerToIRVMService {
ReadOnlyList.wrap(operations))); ReadOnlyList.wrap(operations)));
} }
final var module = new IRVMModule(vmProfile, ReadOnlyList.wrap(loweredFunctions)); final var program = new IRVMProgram(
validator.validate(module, false); new IRVMModule(vmProfile, ReadOnlyList.wrap(loweredFunctions)),
return new IRVMProgram(
module,
new BytecodeEmitter.EmissionPlan( new BytecodeEmitter.EmissionPlan(
0, 0,
ReadOnlyList.empty(), ReadOnlyList.empty(),
ReadOnlyList.empty(), ReadOnlyList.empty(),
ReadOnlyList.wrap(emissionFunctions))); ReadOnlyList.wrap(emissionFunctions)));
validator.validate(program, false);
return program;
} }
private ReadOnlyList<IRBackendExecutableFunction> orderFunctions( private ReadOnlyList<IRBackendExecutableFunction> orderFunctions(

View File

@ -37,7 +37,7 @@ public class OptimizeIRVMService {
throw new IllegalArgumentException("unsupported vm profile: " + program.module().vmProfile()); throw new IllegalArgumentException("unsupported vm profile: " + program.module().vmProfile());
} }
var current = program; var current = program;
validator.validate(current.module(), false); validator.validate(current, false);
for (final var pass : passes) { for (final var pass : passes) {
if (pass == null || !pass.enabled()) { if (pass == null || !pass.enabled()) {
continue; continue;
@ -47,7 +47,7 @@ public class OptimizeIRVMService {
if (!beforeProfile.equals(current.module().vmProfile())) { if (!beforeProfile.equals(current.module().vmProfile())) {
throw new IllegalArgumentException("pass changed vm profile: " + pass.name()); throw new IllegalArgumentException("pass changed vm profile: " + pass.name());
} }
validator.validate(current.module(), false); validator.validate(current, false);
} }
return current; return current;
} }

View File

@ -69,6 +69,8 @@ class GoldenArtifactsTest {
"", "",
null, null,
new IRBackendExecutableFunction.IntrinsicCallMetadata("core.color.pack", 1, new IntrinsicId(0)), new IRBackendExecutableFunction.IntrinsicCallMetadata("core.color.pack", 1, new IntrinsicId(0)),
0,
0,
Span.none()), Span.none()),
new IRBackendExecutableFunction.Instruction( new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.RET, IRBackendExecutableFunction.InstructionKind.RET,

View File

@ -1,6 +1,8 @@
package p.studio.compiler.backend.irvm; package p.studio.compiler.backend.irvm;
import org.junit.jupiter.api.Test; 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 p.studio.utilities.structures.ReadOnlyList;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ -122,4 +124,71 @@ class IRVMValidatorTest {
assertDoesNotThrow(() -> validator.validate(module, false)); 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() final var backend = IRBackend.builder()
.executableFunctions(ReadOnlyList.from( .executableFunctions(ReadOnlyList.from(
fn("main", "app", 10, 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), callIntrinsic("core.color.pack", 1, 0),
ret())))) ret()))))
.intrinsicPool(ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1))) .intrinsicPool(ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1)))
@ -240,6 +240,8 @@ class LowerToIRVMServiceTest {
"", "",
null, null,
new IRBackendExecutableFunction.IntrinsicCallMetadata(canonicalName, canonicalVersion, new IntrinsicId(intrinsicId)), new IRBackendExecutableFunction.IntrinsicCallMetadata(canonicalName, canonicalVersion, new IntrinsicId(intrinsicId)),
0,
0,
Span.none()); Span.none());
} }

View File

@ -48,7 +48,7 @@ class BackendGateIIntegrationTest {
void gateI_validHostcallPath() { void gateI_validHostcallPath() {
final var module = emitFromBackend(backendWithSingleFunction( final var module = emitFromBackend(backendWithSingleFunction(
fn("main", ReadOnlyList.from( fn("main", ReadOnlyList.from(
callHost("gfx", "draw_pixel", 1, 2, 0), callHost("gfx", "draw_pixel", 1, 0, 0),
ret())))); ret()))));
final var check = compatibilityAdapter.check(module); final var check = compatibilityAdapter.check(module);
@ -131,7 +131,7 @@ class BackendGateIIntegrationTest {
void gateI_rejectMissingCapability() { void gateI_rejectMissingCapability() {
final var module = emitFromBackend(backendWithSingleFunction( final var module = emitFromBackend(backendWithSingleFunction(
fn("main", ReadOnlyList.from( fn("main", ReadOnlyList.from(
callHost("gfx", "draw_pixel", 1, 2, 0), callHost("gfx", "draw_pixel", 1, 0, 0),
ret())))); ret()))));
final var caps = Set.of("input"); final var caps = Set.of("input");
@ -236,6 +236,8 @@ class BackendGateIIntegrationTest {
"", "",
null, null,
new IRBackendExecutableFunction.IntrinsicCallMetadata(canonicalName, canonicalVersion, new IntrinsicId(intrinsicId)), new IRBackendExecutableFunction.IntrinsicCallMetadata(canonicalName, canonicalVersion, new IntrinsicId(intrinsicId)),
0,
0,
Span.none()); Span.none());
} }

View File

@ -77,10 +77,15 @@ public record IRReservedMetadata(
String sourceMethodName, String sourceMethodName,
String canonicalName, String canonicalName,
long canonicalVersion, long canonicalVersion,
int argSlots,
int retSlots,
Span span) { Span span) {
public IntrinsicSurface { public IntrinsicSurface {
sourceMethodName = Objects.requireNonNull(sourceMethodName, "sourceMethodName"); sourceMethodName = Objects.requireNonNull(sourceMethodName, "sourceMethodName");
canonicalName = Objects.requireNonNull(canonicalName, "canonicalName"); 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"); span = Objects.requireNonNull(span, "span");
} }
} }