implements PR-O2.4

This commit is contained in:
bQUARKz 2026-03-07 18:23:09 +00:00
parent 7679c5eeeb
commit ea27561e65
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
5 changed files with 135 additions and 87 deletions

View File

@ -20,7 +20,6 @@ import p.studio.utilities.structures.ReadOnlyList;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -31,6 +30,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
class BackendGateIIntegrationTest { class BackendGateIIntegrationTest {
private final RuntimeCompatibilityAdapter compatibilityAdapter = new LocalRuntimeCompatibilityAdapter();
@Test @Test
void gateI_syscSectionPresentAndEmpty() { void gateI_syscSectionPresentAndEmpty() {
@ -38,7 +38,7 @@ class BackendGateIIntegrationTest {
fn("main", ReadOnlyList.from(ret())))); fn("main", ReadOnlyList.from(ret()))));
assertTrue(module.syscalls().isEmpty()); assertTrue(module.syscalls().isEmpty());
assertEquals(CompatibilityError.NONE, new RuntimePreloadCompatibilityChecker().check(module).error()); assertEquals(CompatibilityError.NONE, compatibilityAdapter.check(module).error());
} }
@Test @Test
@ -48,9 +48,11 @@ class BackendGateIIntegrationTest {
callHost("gfx", "draw_pixel", 1, 2, 0), callHost("gfx", "draw_pixel", 1, 2, 0),
ret())))); ret()))));
final var check = new RuntimePreloadCompatibilityChecker().check(module); final var check = compatibilityAdapter.check(module);
assertEquals(CompatibilityError.NONE, check.error()); assertEquals(CompatibilityError.NONE, check.error());
assertTrue(check.hostcallCount() > 0); assertTrue(check.hostcallCount() > 0);
assertEquals("local", check.adapterMode());
assertTrue(!check.runtimeLine().isBlank());
} }
@Test @Test
@ -64,7 +66,7 @@ class BackendGateIIntegrationTest {
ReadOnlyList.empty(), ReadOnlyList.empty(),
ReadOnlyList.from(new BytecodeModule.SyscallDecl("gfx", "draw_pixel", 1, 1, 0))); ReadOnlyList.from(new BytecodeModule.SyscallDecl("gfx", "draw_pixel", 1, 1, 0)));
final var check = new RuntimePreloadCompatibilityChecker().check(module); final var check = compatibilityAdapter.check(module);
assertEquals(CompatibilityError.HOSTCALL_INDEX_OUT_OF_BOUNDS, check.error()); assertEquals(CompatibilityError.HOSTCALL_INDEX_OUT_OF_BOUNDS, check.error());
} }
@ -79,7 +81,7 @@ class BackendGateIIntegrationTest {
ReadOnlyList.empty(), ReadOnlyList.empty(),
ReadOnlyList.from(new BytecodeModule.SyscallDecl("gfx", "draw_pixel", 1, 1, 0))); ReadOnlyList.from(new BytecodeModule.SyscallDecl("gfx", "draw_pixel", 1, 1, 0)));
final var check = new RuntimePreloadCompatibilityChecker().check(module); final var check = compatibilityAdapter.check(module);
assertEquals(CompatibilityError.UNUSED_SYSC_DECL, check.error()); assertEquals(CompatibilityError.UNUSED_SYSC_DECL, check.error());
} }
@ -94,7 +96,7 @@ class BackendGateIIntegrationTest {
ReadOnlyList.empty(), ReadOnlyList.empty(),
ReadOnlyList.empty()); ReadOnlyList.empty());
final var check = new RuntimePreloadCompatibilityChecker().check(module); final var check = compatibilityAdapter.check(module);
assertEquals(CompatibilityError.RAW_SYSCALL_IN_PRELOAD, check.error()); assertEquals(CompatibilityError.RAW_SYSCALL_IN_PRELOAD, check.error());
} }
@ -131,7 +133,7 @@ class BackendGateIIntegrationTest {
final var caps = Set.of("input"); final var caps = Set.of("input");
final var required = new HashMap<String, String>(); final var required = new HashMap<String, String>();
required.put("gfx::draw_pixel::1", "gfx"); required.put("gfx::draw_pixel::1", "gfx");
final var check = new RuntimePreloadCompatibilityChecker(caps, required).check(module); final var check = new LocalRuntimeCompatibilityAdapter(caps, required).check(module);
assertEquals(CompatibilityError.MISSING_CAPABILITY, check.error()); assertEquals(CompatibilityError.MISSING_CAPABILITY, check.error());
} }
@ -142,7 +144,7 @@ class BackendGateIIntegrationTest {
callIntrinsic("core.color.pack", 1, 0x2000), callIntrinsic("core.color.pack", 1, 0x2000),
ret())))); ret()))));
final var check = new RuntimePreloadCompatibilityChecker().check(module); final var check = compatibilityAdapter.check(module);
assertEquals(CompatibilityError.NONE, check.error()); assertEquals(CompatibilityError.NONE, check.error());
assertTrue(module.syscalls().isEmpty()); assertTrue(module.syscalls().isEmpty());
} }
@ -244,83 +246,4 @@ class BackendGateIIntegrationTest {
out.putShort((short) 0x51); out.putShort((short) 0x51);
return out.array(); return out.array();
} }
private enum CompatibilityError {
NONE,
RAW_SYSCALL_IN_PRELOAD,
HOSTCALL_INDEX_OUT_OF_BOUNDS,
UNUSED_SYSC_DECL,
MISSING_CAPABILITY,
}
private record CompatibilityResult(
CompatibilityError error,
int hostcallCount) {
}
private static final class RuntimePreloadCompatibilityChecker {
private final Set<String> capabilities;
private final Map<String, String> requiredCapabilityBySyscallIdentity;
private RuntimePreloadCompatibilityChecker() {
this(Set.of("gfx", "input", "audio", "system"), Map.of());
}
private RuntimePreloadCompatibilityChecker(
final Set<String> capabilities,
final Map<String, String> requiredCapabilityBySyscallIdentity) {
this.capabilities = capabilities == null ? Set.of() : new HashSet<>(capabilities);
this.requiredCapabilityBySyscallIdentity = requiredCapabilityBySyscallIdentity == null
? Map.of()
: new HashMap<>(requiredCapabilityBySyscallIdentity);
}
CompatibilityResult check(final BytecodeModule module) {
final var used = new boolean[module.syscalls().size()];
var hostcallCount = 0;
var pc = 0;
while (pc < module.code().length) {
final var opcode = readU16(module.code(), pc);
switch (opcode) {
case 0x70 -> {
return new CompatibilityResult(CompatibilityError.RAW_SYSCALL_IN_PRELOAD, hostcallCount);
}
case 0x71 -> {
final var syscIndex = readU32(module.code(), pc + 2);
if (syscIndex < 0 || syscIndex >= module.syscalls().size()) {
return new CompatibilityResult(CompatibilityError.HOSTCALL_INDEX_OUT_OF_BOUNDS, hostcallCount);
}
used[syscIndex] = true;
hostcallCount++;
final var decl = module.syscalls().get(syscIndex);
final var identity = decl.module() + "::" + decl.name() + "::" + decl.version();
final var requiredCap = requiredCapabilityBySyscallIdentity.get(identity);
if (requiredCap != null && !capabilities.contains(requiredCap)) {
return new CompatibilityResult(CompatibilityError.MISSING_CAPABILITY, hostcallCount);
}
pc += 6;
}
case 0x50, 0x72, 0x02, 0x03, 0x04, 0x10, 0x18, 0x40, 0x41, 0x42, 0x43, 0x56 -> pc += 6;
case 0x14, 0x15, 0x52, 0x54 -> pc += 10;
case 0x16 -> pc += 3;
default -> pc += 2;
}
}
for (var i = 0; i < used.length; i++) {
if (!used[i]) {
return new CompatibilityResult(CompatibilityError.UNUSED_SYSC_DECL, hostcallCount);
}
}
return new CompatibilityResult(CompatibilityError.NONE, hostcallCount);
}
private int readU16(final byte[] code, final int offset) {
return ByteBuffer.wrap(code, offset, 2).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF;
}
private int readU32(final byte[] code, final int offset) {
return ByteBuffer.wrap(code, offset, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
}
}
} }

View File

@ -0,0 +1,9 @@
package p.studio.compiler.integration;
enum CompatibilityError {
NONE,
RAW_SYSCALL_IN_PRELOAD,
HOSTCALL_INDEX_OUT_OF_BOUNDS,
UNUSED_SYSC_DECL,
MISSING_CAPABILITY,
}

View File

@ -0,0 +1,8 @@
package p.studio.compiler.integration;
record CompatibilityResult(
CompatibilityError error,
int hostcallCount,
String runtimeLine,
String adapterMode) {
}

View File

@ -0,0 +1,101 @@
package p.studio.compiler.integration;
import p.studio.compiler.backend.bytecode.BytecodeModule;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
final class LocalRuntimeCompatibilityAdapter implements RuntimeCompatibilityAdapter {
private final Set<String> capabilities;
private final Map<String, String> requiredCapabilityBySyscallIdentity;
private final String runtimeLine;
LocalRuntimeCompatibilityAdapter() {
this(Set.of("gfx", "input", "audio", "system"), Map.of(), "runtime-local-sim-v1");
}
LocalRuntimeCompatibilityAdapter(
final Set<String> capabilities,
final Map<String, String> requiredCapabilityBySyscallIdentity) {
this(capabilities, requiredCapabilityBySyscallIdentity, "runtime-local-sim-v1");
}
LocalRuntimeCompatibilityAdapter(
final Set<String> capabilities,
final Map<String, String> requiredCapabilityBySyscallIdentity,
final String runtimeLine) {
this.capabilities = capabilities == null ? Set.of() : new HashSet<>(capabilities);
this.requiredCapabilityBySyscallIdentity = requiredCapabilityBySyscallIdentity == null
? Map.of()
: new HashMap<>(requiredCapabilityBySyscallIdentity);
this.runtimeLine = runtimeLine == null || runtimeLine.isBlank() ? "runtime-local-sim-v1" : runtimeLine;
}
@Override
public CompatibilityResult check(final BytecodeModule module) {
final var used = new boolean[module.syscalls().size()];
var hostcallCount = 0;
var pc = 0;
while (pc < module.code().length) {
final var opcode = readU16(module.code(), pc);
switch (opcode) {
case 0x70 -> {
return new CompatibilityResult(
CompatibilityError.RAW_SYSCALL_IN_PRELOAD,
hostcallCount,
runtimeLine,
"local");
}
case 0x71 -> {
final var syscIndex = readU32(module.code(), pc + 2);
if (syscIndex < 0 || syscIndex >= module.syscalls().size()) {
return new CompatibilityResult(
CompatibilityError.HOSTCALL_INDEX_OUT_OF_BOUNDS,
hostcallCount,
runtimeLine,
"local");
}
used[syscIndex] = true;
hostcallCount++;
final var decl = module.syscalls().get(syscIndex);
final var identity = decl.module() + "::" + decl.name() + "::" + decl.version();
final var requiredCap = requiredCapabilityBySyscallIdentity.get(identity);
if (requiredCap != null && !capabilities.contains(requiredCap)) {
return new CompatibilityResult(
CompatibilityError.MISSING_CAPABILITY,
hostcallCount,
runtimeLine,
"local");
}
pc += 6;
}
case 0x50, 0x72, 0x02, 0x03, 0x04, 0x10, 0x18, 0x40, 0x41, 0x42, 0x43, 0x56 -> pc += 6;
case 0x14, 0x15, 0x52, 0x54 -> pc += 10;
case 0x16 -> pc += 3;
default -> pc += 2;
}
}
for (var i = 0; i < used.length; i++) {
if (!used[i]) {
return new CompatibilityResult(
CompatibilityError.UNUSED_SYSC_DECL,
hostcallCount,
runtimeLine,
"local");
}
}
return new CompatibilityResult(CompatibilityError.NONE, hostcallCount, runtimeLine, "local");
}
private int readU16(final byte[] code, final int offset) {
return ByteBuffer.wrap(code, offset, 2).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF;
}
private int readU32(final byte[] code, final int offset) {
return ByteBuffer.wrap(code, offset, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
}
}

View File

@ -0,0 +1,7 @@
package p.studio.compiler.integration;
import p.studio.compiler.backend.bytecode.BytecodeModule;
interface RuntimeCompatibilityAdapter {
CompatibilityResult check(BytecodeModule module);
}