implements PR-O4.7

This commit is contained in:
bQUARKz 2026-03-07 19:48:57 +00:00
parent 4c52f5de8d
commit a65018f72c
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
7 changed files with 209 additions and 8 deletions

View File

@ -31,7 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertThrows;
class BackendGateIIntegrationTest {
private final RuntimeCompatibilityAdapter compatibilityAdapter = new LocalRuntimeCompatibilityAdapter();
private final RuntimeCompatibilityAdapter compatibilityAdapter = RuntimeCompatibilityAdapters.createDefault();
@Test
void gateI_syscSectionPresentAndEmpty() {
@ -52,8 +52,9 @@ class BackendGateIIntegrationTest {
final var check = compatibilityAdapter.check(module);
assertEquals(CompatibilityError.NONE, check.error());
assertTrue(check.hostcallCount() > 0);
assertEquals("local", check.adapterMode());
assertTrue(Set.of("runtime-backed", "local-fallback", "local").contains(check.adapterMode()));
assertTrue(!check.runtimeLine().isBlank());
assertTrue(!check.reason().isBlank());
}
@Test

View File

@ -6,4 +6,5 @@ enum CompatibilityError {
HOSTCALL_INDEX_OUT_OF_BOUNDS,
UNUSED_SYSC_DECL,
MISSING_CAPABILITY,
RUNTIME_REJECTED,
}

View File

@ -4,5 +4,10 @@ record CompatibilityResult(
CompatibilityError error,
int hostcallCount,
String runtimeLine,
String adapterMode) {
String adapterMode,
String reason) {
CompatibilityResult {
reason = reason == null ? "" : reason;
}
}

View File

@ -48,7 +48,8 @@ final class LocalRuntimeCompatibilityAdapter implements RuntimeCompatibilityAdap
CompatibilityError.RAW_SYSCALL_IN_PRELOAD,
hostcallCount,
runtimeLine,
"local");
"local",
"raw syscall opcode is forbidden");
}
case 0x71 -> {
final var syscIndex = readU32(module.code(), pc + 2);
@ -57,7 +58,8 @@ final class LocalRuntimeCompatibilityAdapter implements RuntimeCompatibilityAdap
CompatibilityError.HOSTCALL_INDEX_OUT_OF_BOUNDS,
hostcallCount,
runtimeLine,
"local");
"local",
"hostcall index out of bounds");
}
used[syscIndex] = true;
hostcallCount++;
@ -69,7 +71,8 @@ final class LocalRuntimeCompatibilityAdapter implements RuntimeCompatibilityAdap
CompatibilityError.MISSING_CAPABILITY,
hostcallCount,
runtimeLine,
"local");
"local",
"required capability is missing");
}
pc += 6;
}
@ -85,10 +88,11 @@ final class LocalRuntimeCompatibilityAdapter implements RuntimeCompatibilityAdap
CompatibilityError.UNUSED_SYSC_DECL,
hostcallCount,
runtimeLine,
"local");
"local",
"unused syscall declaration");
}
}
return new CompatibilityResult(CompatibilityError.NONE, hostcallCount, runtimeLine, "local");
return new CompatibilityResult(CompatibilityError.NONE, hostcallCount, runtimeLine, "local", "ok");
}
private int readU16(final byte[] code, final int offset) {

View File

@ -0,0 +1,137 @@
package p.studio.compiler.integration;
import p.studio.compiler.backend.bytecode.BytecodeModule;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
final class RuntimeBackedCompatibilityAdapter implements RuntimeCompatibilityAdapter {
private static final String DEFAULT_RUNTIME_LINE = "runtime-line-unknown";
private static final String FALLBACK_MODE = "local-fallback";
private final RuntimeCompatibilityAdapter fallback;
private final Path runtimeRoot;
private final String runtimeLine;
private final String runtimeCheckCommand;
RuntimeBackedCompatibilityAdapter(final RuntimeCompatibilityAdapter fallback) {
this(
fallback == null ? new LocalRuntimeCompatibilityAdapter() : fallback,
Path.of("../runtime"),
System.getenv("PROMETEU_RUNTIME_CHECK_CMD"));
}
RuntimeBackedCompatibilityAdapter(
final RuntimeCompatibilityAdapter fallback,
final Path runtimeRoot,
final String runtimeCheckCommand) {
this.fallback = fallback == null ? new LocalRuntimeCompatibilityAdapter() : fallback;
this.runtimeRoot = runtimeRoot == null ? Path.of("../runtime") : runtimeRoot;
this.runtimeLine = readRuntimeLine(this.runtimeRoot);
this.runtimeCheckCommand = runtimeCheckCommand == null ? "" : runtimeCheckCommand.trim();
}
@Override
public CompatibilityResult check(final BytecodeModule module) {
final var local = fallback.check(module);
if (runtimeCheckCommand.isBlank()) {
return fallbackWithReason(local, "runtime command not configured");
}
if (!Files.isDirectory(runtimeRoot)) {
return fallbackWithReason(local, "runtime root is unavailable: " + runtimeRoot);
}
Path artifact = null;
try {
artifact = Files.createTempFile("gate-i-runtime-", ".pbx");
Files.write(artifact, module.serialize());
final var command = renderCommand(runtimeCheckCommand, artifact);
final var process = new ProcessBuilder(List.of("sh", "-lc", command))
.directory(runtimeRoot.toFile())
.redirectErrorStream(true)
.start();
final var output = new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8).trim();
final var exitCode = process.waitFor();
if (exitCode != 0) {
return new CompatibilityResult(
CompatibilityError.RUNTIME_REJECTED,
local.hostcallCount(),
runtimeLine,
"runtime-backed",
"runtime command rejected artifact (exit=%d): %s".formatted(exitCode, shrink(output)));
}
return new CompatibilityResult(
local.error(),
local.hostcallCount(),
runtimeLine,
"runtime-backed",
"runtime command accepted artifact");
} catch (IOException | InterruptedException e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
return fallbackWithReason(local, "runtime command failed: " + e.getMessage());
} finally {
if (artifact != null) {
try {
Files.deleteIfExists(artifact);
} catch (IOException ignored) {
}
}
}
}
private CompatibilityResult fallbackWithReason(
final CompatibilityResult local,
final String reason) {
return new CompatibilityResult(
local.error(),
local.hostcallCount(),
runtimeLine,
FALLBACK_MODE,
reason);
}
private String renderCommand(
final String commandTemplate,
final Path artifactPath) {
final var quotedPath = shellQuote(artifactPath.toAbsolutePath().toString());
if (commandTemplate.contains("{artifact}")) {
return commandTemplate.replace("{artifact}", quotedPath);
}
return commandTemplate + " " + quotedPath;
}
private String shellQuote(final String value) {
return "'" + value.replace("'", "'\"'\"'") + "'";
}
private static String readRuntimeLine(final Path runtimeRoot) {
final var versionFile = runtimeRoot.resolve("VERSION.txt");
if (!Files.exists(versionFile)) {
return DEFAULT_RUNTIME_LINE;
}
try {
final var content = Files.readString(versionFile).trim();
if (content.isBlank()) {
return DEFAULT_RUNTIME_LINE;
}
return "runtime-v" + content;
} catch (IOException e) {
return DEFAULT_RUNTIME_LINE;
}
}
private String shrink(final String output) {
if (output == null || output.isBlank()) {
return "<no output>";
}
if (output.length() <= 160) {
return output;
}
return output.substring(0, 160) + "...";
}
}

View File

@ -0,0 +1,43 @@
package p.studio.compiler.integration;
import org.junit.jupiter.api.Test;
import p.studio.compiler.backend.bytecode.BytecodeModule;
import p.studio.utilities.structures.ReadOnlyList;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class RuntimeBackedCompatibilityAdapterTest {
@Test
void checkMustFallbackToLocalModeWhenRuntimeCommandIsUnavailable() {
final var adapter = new RuntimeBackedCompatibilityAdapter(
new LocalRuntimeCompatibilityAdapter(),
Path.of("./missing-runtime"),
"");
final var module = new BytecodeModule(
0,
ReadOnlyList.empty(),
ReadOnlyList.from(new BytecodeModule.FunctionMeta(0, 2, 0, 0, 0, 1)),
codeRet(),
null,
ReadOnlyList.empty(),
ReadOnlyList.empty());
final var result = adapter.check(module);
assertEquals(CompatibilityError.NONE, result.error());
assertEquals("local-fallback", result.adapterMode());
assertTrue(!result.reason().isBlank());
assertTrue(!result.runtimeLine().isBlank());
}
private byte[] codeRet() {
final var out = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN);
out.putShort((short) 0x51);
return out.array();
}
}

View File

@ -0,0 +1,10 @@
package p.studio.compiler.integration;
final class RuntimeCompatibilityAdapters {
private RuntimeCompatibilityAdapters() {
}
static RuntimeCompatibilityAdapter createDefault() {
return new RuntimeBackedCompatibilityAdapter(new LocalRuntimeCompatibilityAdapter());
}
}