implements PR-O1.2

This commit is contained in:
bQUARKz 2026-03-07 17:44:37 +00:00
parent b5c372efb2
commit 9e3d9ccb93
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
2 changed files with 109 additions and 2 deletions

View File

@ -23,7 +23,22 @@ public record IRBackendExecutableFunction(
fileId = Objects.requireNonNull(fileId, "fileId");
moduleKey = moduleKey == null ? "" : moduleKey;
callableName = Objects.requireNonNull(callableName, "callableName");
if (callableName.isBlank()) {
throw new IllegalArgumentException("callableName must not be blank");
}
if (sourceStart < 0 || sourceEnd < 0 || sourceEnd < sourceStart) {
throw new IllegalArgumentException("invalid source span bounds");
}
if (paramSlots < 0 || localSlots < 0 || returnSlots < 0 || maxStackSlots < 0) {
throw new IllegalArgumentException("slots must be non-negative");
}
if (maxStackSlots < returnSlots) {
throw new IllegalArgumentException("maxStackSlots must be >= returnSlots");
}
instructions = instructions == null ? ReadOnlyList.empty() : instructions;
for (final var instruction : instructions) {
Objects.requireNonNull(instruction, "instruction");
}
span = span == null ? Span.none() : span;
}
@ -33,29 +48,62 @@ public record IRBackendExecutableFunction(
String calleeCallableName,
HostCallMetadata hostCall,
IntrinsicCallMetadata intrinsicCall,
Integer expectedArgSlots,
Integer expectedRetSlots,
Span span) {
public Instruction(
final InstructionKind kind,
final String calleeModuleKey,
final String calleeCallableName,
final HostCallMetadata hostCall,
final IntrinsicCallMetadata intrinsicCall,
final Span span) {
this(kind, calleeModuleKey, calleeCallableName, hostCall, intrinsicCall, null, null, span);
}
public Instruction {
Objects.requireNonNull(kind, "kind");
span = span == null ? Span.none() : span;
calleeModuleKey = calleeModuleKey == null ? "" : calleeModuleKey;
calleeCallableName = calleeCallableName == null ? "" : calleeCallableName;
if (expectedArgSlots != null && expectedArgSlots < 0) {
throw new IllegalArgumentException("expectedArgSlots must be non-negative");
}
if (expectedRetSlots != null && expectedRetSlots < 0) {
throw new IllegalArgumentException("expectedRetSlots must be non-negative");
}
switch (kind) {
case CALL_FUNC -> {
if (calleeCallableName.isBlank()) {
throw new IllegalArgumentException("CALL_FUNC requires calleeCallableName");
}
if (hostCall != null || intrinsicCall != null) {
throw new IllegalArgumentException("CALL_FUNC must not carry host or intrinsic metadata");
}
}
case CALL_HOST -> {
if (hostCall == null) {
throw new IllegalArgumentException("CALL_HOST requires hostCall metadata");
}
if (intrinsicCall != null) {
throw new IllegalArgumentException("CALL_HOST must not carry intrinsic metadata");
}
}
case CALL_INTRINSIC -> {
if (intrinsicCall == null) {
throw new IllegalArgumentException("CALL_INTRINSIC requires intrinsic metadata");
}
if (hostCall != null) {
throw new IllegalArgumentException("CALL_INTRINSIC must not carry host metadata");
}
}
case HALT, RET -> {
if (!calleeCallableName.isBlank() || hostCall != null || intrinsicCall != null) {
throw new IllegalArgumentException(kind + " must not carry callsite metadata");
}
if (expectedArgSlots != null || expectedRetSlots != null) {
throw new IllegalArgumentException(kind + " must not carry expected slot metadata");
}
}
}
}
@ -78,6 +126,12 @@ public record IRBackendExecutableFunction(
public HostCallMetadata {
module = Objects.requireNonNull(module, "module");
name = Objects.requireNonNull(name, "name");
if (module.isBlank() || name.isBlank()) {
throw new IllegalArgumentException("module and name must not be blank");
}
if (version < 0 || argSlots < 0 || retSlots < 0) {
throw new IllegalArgumentException("host metadata values must be non-negative");
}
}
}
@ -87,7 +141,12 @@ public record IRBackendExecutableFunction(
int intrinsicId) {
public IntrinsicCallMetadata {
canonicalName = Objects.requireNonNull(canonicalName, "canonicalName");
if (canonicalName.isBlank()) {
throw new IllegalArgumentException("canonicalName must not be blank");
}
if (canonicalVersion < 0) {
throw new IllegalArgumentException("canonicalVersion must be non-negative");
}
}
}
}

View File

@ -7,6 +7,7 @@ import p.studio.utilities.structures.ReadOnlyList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
class IRBackendExecutableContractTest {
@ -30,6 +31,54 @@ class IRBackendExecutableContractTest {
assertEquals(IRBackendExecutableFunction.InstructionKind.CALL_FUNC, callFunc.kind());
}
@Test
void functionContractMustRejectInvalidSlotAndSpanBounds() {
assertThrows(IllegalArgumentException.class, () -> new IRBackendExecutableFunction(
new FileId(1),
"app",
"main",
10,
5,
0,
0,
0,
1,
ReadOnlyList.empty(),
Span.none()));
final var thrown = assertThrows(IllegalArgumentException.class, () -> new IRBackendExecutableFunction(
new FileId(1),
"app",
"main",
0,
10,
0,
0,
2,
1,
ReadOnlyList.empty(),
Span.none()));
assertTrue(thrown.getMessage().contains("maxStackSlots"));
}
@Test
void instructionContractMustRejectMixedMetadataKinds() {
assertThrows(IllegalArgumentException.class, () -> new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
"app/main",
"foo",
new IRBackendExecutableFunction.HostCallMetadata("gfx", "draw", 1, 0, 0),
null,
Span.none()));
assertThrows(IllegalArgumentException.class, () -> new IRBackendExecutableFunction.HostCallMetadata(
"gfx",
"draw",
1,
-1,
0));
}
@Test
void aggregatorMustPreserveExecutableFunctionOrderDeterministically() {
final var fileA = new IRBackendFile(
@ -87,4 +136,3 @@ class IRBackendExecutableContractTest {
assertEquals("aux", backend.getExecutableFunctions().get(1).callableName());
}
}