implements PR-05.5
This commit is contained in:
parent
6a7cd6a40a
commit
48ce448203
@ -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;
|
||||
}
|
||||
|
||||
@ -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()));
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user