implements PR-08.5
This commit is contained in:
parent
505a4f72ce
commit
12a6602b0a
@ -1,11 +1,16 @@
|
||||
package p.studio.compiler.backend.irvm;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
final class IRVMIntrinsicRegistry {
|
||||
private static final Map<IntrinsicKey, Integer> FINAL_ID_BY_INTRINSIC = createRegistry();
|
||||
private static final String REGISTRY_RESOURCE = "/intrinsics/registry-v1.csv";
|
||||
private static final Map<IntrinsicKey, Integer> FINAL_ID_BY_INTRINSIC = loadRegistry();
|
||||
|
||||
private IRVMIntrinsicRegistry() {
|
||||
}
|
||||
@ -23,38 +28,79 @@ final class IRVMIntrinsicRegistry {
|
||||
return OptionalInt.of(resolved);
|
||||
}
|
||||
|
||||
private static Map<IntrinsicKey, Integer> createRegistry() {
|
||||
static Map<String, Integer> snapshotByCanonicalIdentity() {
|
||||
final var snapshot = new HashMap<String, Integer>(FINAL_ID_BY_INTRINSIC.size());
|
||||
for (final var entry : FINAL_ID_BY_INTRINSIC.entrySet()) {
|
||||
snapshot.put(canonicalIdentity(entry.getKey().canonicalName(), entry.getKey().canonicalVersion()), entry.getValue());
|
||||
}
|
||||
return Map.copyOf(snapshot);
|
||||
}
|
||||
|
||||
private static Map<IntrinsicKey, Integer> loadRegistry() {
|
||||
final var stream = IRVMIntrinsicRegistry.class.getResourceAsStream(REGISTRY_RESOURCE);
|
||||
if (stream == null) {
|
||||
throw new IllegalStateException("missing intrinsic registry resource: " + REGISTRY_RESOURCE);
|
||||
}
|
||||
|
||||
final var registry = new HashMap<IntrinsicKey, Integer>();
|
||||
register(registry, "vec2.dot", 1, 0x1000);
|
||||
register(registry, "vec2.length", 1, 0x1001);
|
||||
|
||||
register(registry, "input.pad", 1, 0x2000);
|
||||
register(registry, "input.touch", 1, 0x2001);
|
||||
|
||||
register(registry, "input.pad.up", 1, 0x2010);
|
||||
register(registry, "input.pad.down", 1, 0x2011);
|
||||
register(registry, "input.pad.left", 1, 0x2012);
|
||||
register(registry, "input.pad.right", 1, 0x2013);
|
||||
register(registry, "input.pad.a", 1, 0x2014);
|
||||
register(registry, "input.pad.b", 1, 0x2015);
|
||||
register(registry, "input.pad.x", 1, 0x2016);
|
||||
register(registry, "input.pad.y", 1, 0x2017);
|
||||
register(registry, "input.pad.l", 1, 0x2018);
|
||||
register(registry, "input.pad.r", 1, 0x2019);
|
||||
register(registry, "input.pad.start", 1, 0x201A);
|
||||
register(registry, "input.pad.select", 1, 0x201B);
|
||||
|
||||
register(registry, "input.touch.button", 1, 0x2020);
|
||||
register(registry, "input.touch.x", 1, 0x2021);
|
||||
register(registry, "input.touch.y", 1, 0x2022);
|
||||
|
||||
register(registry, "input.button.pressed", 1, 0x2030);
|
||||
register(registry, "input.button.released", 1, 0x2031);
|
||||
register(registry, "input.button.down", 1, 0x2032);
|
||||
register(registry, "input.button.hold", 1, 0x2033);
|
||||
final var identityByFinalId = new HashMap<Integer, String>();
|
||||
try (final var reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
|
||||
String rawLine;
|
||||
int lineNumber = 0;
|
||||
while ((rawLine = reader.readLine()) != null) {
|
||||
lineNumber++;
|
||||
final var line = rawLine.trim();
|
||||
if (line.isBlank() || line.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
final var columns = line.split(",", -1);
|
||||
if (columns.length != 3) {
|
||||
throw new IllegalStateException("invalid intrinsic registry row at line " + lineNumber + ": " + line);
|
||||
}
|
||||
final var canonicalName = columns[0].trim();
|
||||
final long canonicalVersion;
|
||||
try {
|
||||
canonicalVersion = Long.parseLong(columns[1].trim());
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new IllegalStateException("invalid intrinsic version at line " + lineNumber + ": " + line, ex);
|
||||
}
|
||||
final int finalId = parseFinalId(columns[2].trim(), lineNumber, line);
|
||||
register(registry, canonicalName, canonicalVersion, finalId);
|
||||
final var identity = canonicalIdentity(canonicalName, canonicalVersion);
|
||||
final var previous = identityByFinalId.putIfAbsent(finalId, identity);
|
||||
if (previous != null && !previous.equals(identity)) {
|
||||
throw new IllegalStateException(
|
||||
"duplicate intrinsic final id 0x%08X for %s and %s".formatted(finalId, previous, identity));
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException("failed to read intrinsic registry resource: " + REGISTRY_RESOURCE, ex);
|
||||
}
|
||||
return Map.copyOf(registry);
|
||||
}
|
||||
|
||||
private static int parseFinalId(
|
||||
final String rawId,
|
||||
final int lineNumber,
|
||||
final String line) {
|
||||
try {
|
||||
if (rawId.startsWith("0x") || rawId.startsWith("0X")) {
|
||||
return Integer.parseUnsignedInt(rawId.substring(2), 16);
|
||||
}
|
||||
return Integer.parseUnsignedInt(rawId, 10);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new IllegalStateException(
|
||||
"invalid intrinsic final id at line %d: %s".formatted(lineNumber, line),
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static String canonicalIdentity(
|
||||
final String canonicalName,
|
||||
final long canonicalVersion) {
|
||||
return canonicalName + "@" + canonicalVersion;
|
||||
}
|
||||
|
||||
private static void register(
|
||||
final Map<IntrinsicKey, Integer> registry,
|
||||
final String canonicalName,
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
# canonicalName,canonicalVersion,finalId
|
||||
vec2.dot,1,0x1000
|
||||
vec2.length,1,0x1001
|
||||
input.pad,1,0x2000
|
||||
input.touch,1,0x2001
|
||||
input.pad.up,1,0x2010
|
||||
input.pad.down,1,0x2011
|
||||
input.pad.left,1,0x2012
|
||||
input.pad.right,1,0x2013
|
||||
input.pad.a,1,0x2014
|
||||
input.pad.b,1,0x2015
|
||||
input.pad.x,1,0x2016
|
||||
input.pad.y,1,0x2017
|
||||
input.pad.l,1,0x2018
|
||||
input.pad.r,1,0x2019
|
||||
input.pad.start,1,0x201A
|
||||
input.pad.select,1,0x201B
|
||||
input.touch.button,1,0x2020
|
||||
input.touch.x,1,0x2021
|
||||
input.touch.y,1,0x2022
|
||||
input.button.pressed,1,0x2030
|
||||
input.button.released,1,0x2031
|
||||
input.button.down,1,0x2032
|
||||
input.button.hold,1,0x2033
|
||||
|
@ -0,0 +1,163 @@
|
||||
package p.studio.compiler.backend.irvm;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
class IRVMIntrinsicRegistryParityTest {
|
||||
private static final Path RUNTIME_INTRINSICS_FILE = Path.of("../runtime/crates/console/prometeu-vm/src/builtins.rs");
|
||||
private static final Pattern OWNER_PATTERN = Pattern.compile("owner:\\s*\"([^\"]+)\",");
|
||||
private static final Pattern NAME_PATTERN = Pattern.compile("name:\\s*\"([^\"]+)\",");
|
||||
private static final Pattern VERSION_PATTERN = Pattern.compile("version:\\s*(\\d+),");
|
||||
private static final Pattern ID_PATTERN = Pattern.compile("id:\\s*(0x[0-9A-Fa-f]+|\\d+),");
|
||||
private static final String STRICT_PARITY_ENV = "PROMETEU_INTRINSIC_PARITY_STRICT";
|
||||
|
||||
@Test
|
||||
void registryResourceMustExposeExpectedCanonicalEntries() {
|
||||
final var snapshot = IRVMIntrinsicRegistry.snapshotByCanonicalIdentity();
|
||||
assertFalse(snapshot.isEmpty());
|
||||
assertEquals(23, snapshot.size());
|
||||
assertEquals(0x2000, snapshot.get("input.pad@1"));
|
||||
assertEquals(0x2033, snapshot.get("input.button.hold@1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registryMustStayInSyncWithRuntimeBuiltinsTable() throws IOException {
|
||||
final var compilerSnapshot = IRVMIntrinsicRegistry.snapshotByCanonicalIdentity();
|
||||
if (!Files.exists(RUNTIME_INTRINSICS_FILE)) {
|
||||
if (strictParityEnabled()) {
|
||||
fail("runtime intrinsic table not found: " + RUNTIME_INTRINSICS_FILE.toAbsolutePath());
|
||||
}
|
||||
return;
|
||||
}
|
||||
final var runtimeSnapshot = parseRuntimeIntrinsicSnapshot(RUNTIME_INTRINSICS_FILE);
|
||||
assertEquals(runtimeSnapshot, compilerSnapshot, parityDiff(runtimeSnapshot, compilerSnapshot));
|
||||
}
|
||||
|
||||
private Map<String, Integer> parseRuntimeIntrinsicSnapshot(final Path runtimeFile) throws IOException {
|
||||
final var runtimeSource = Files.readAllLines(runtimeFile, StandardCharsets.UTF_8);
|
||||
final var mapped = new LinkedHashMap<String, Integer>();
|
||||
var insideIntrinsicsArray = false;
|
||||
var insideEntry = false;
|
||||
String owner = null;
|
||||
String name = null;
|
||||
Long version = null;
|
||||
Integer id = null;
|
||||
for (final var rawLine : runtimeSource) {
|
||||
final var line = rawLine.trim();
|
||||
if (!insideIntrinsicsArray) {
|
||||
if (line.startsWith("const INTRINSICS:")) {
|
||||
insideIntrinsicsArray = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (line.equals("];")) {
|
||||
break;
|
||||
}
|
||||
if (line.startsWith("IntrinsicMeta {")) {
|
||||
insideEntry = true;
|
||||
owner = null;
|
||||
name = null;
|
||||
version = null;
|
||||
id = null;
|
||||
continue;
|
||||
}
|
||||
if (!insideEntry) {
|
||||
continue;
|
||||
}
|
||||
final var ownerMatcher = OWNER_PATTERN.matcher(line);
|
||||
if (ownerMatcher.find()) {
|
||||
owner = ownerMatcher.group(1);
|
||||
}
|
||||
final var nameMatcher = NAME_PATTERN.matcher(line);
|
||||
if (nameMatcher.find()) {
|
||||
name = nameMatcher.group(1);
|
||||
}
|
||||
final var versionMatcher = VERSION_PATTERN.matcher(line);
|
||||
if (versionMatcher.find()) {
|
||||
version = Long.parseLong(versionMatcher.group(1));
|
||||
}
|
||||
final var idMatcher = ID_PATTERN.matcher(line);
|
||||
if (idMatcher.find()) {
|
||||
id = parseRuntimeId(idMatcher.group(1));
|
||||
}
|
||||
if (line.equals("},")) {
|
||||
if (owner == null || name == null || version == null || id == null) {
|
||||
throw new IllegalStateException("invalid runtime intrinsic entry near line: " + rawLine);
|
||||
}
|
||||
final var identity = owner + "." + name + "@" + version;
|
||||
final var previous = mapped.putIfAbsent(identity, id);
|
||||
if (previous != null && previous != id) {
|
||||
throw new IllegalStateException(
|
||||
"runtime intrinsic identity collision for " + identity + ": " + previous + " vs " + id);
|
||||
}
|
||||
insideEntry = false;
|
||||
}
|
||||
}
|
||||
return Map.copyOf(mapped);
|
||||
}
|
||||
|
||||
private int parseRuntimeId(final String rawId) {
|
||||
if (rawId.startsWith("0x") || rawId.startsWith("0X")) {
|
||||
return Integer.parseUnsignedInt(rawId.substring(2), 16);
|
||||
}
|
||||
return Integer.parseUnsignedInt(rawId, 10);
|
||||
}
|
||||
|
||||
private boolean strictParityEnabled() {
|
||||
final var strict = System.getenv(STRICT_PARITY_ENV);
|
||||
if (isTruthy(strict)) {
|
||||
return true;
|
||||
}
|
||||
return isTruthy(System.getenv("CI"));
|
||||
}
|
||||
|
||||
private boolean isTruthy(final String value) {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
return switch (value.trim().toLowerCase()) {
|
||||
case "1", "true", "yes", "on" -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private String parityDiff(
|
||||
final Map<String, Integer> runtimeSnapshot,
|
||||
final Map<String, Integer> compilerSnapshot) {
|
||||
final var missingInCompiler = new ArrayList<String>();
|
||||
final var missingInRuntime = new ArrayList<String>();
|
||||
final var mismatchedIds = new ArrayList<String>();
|
||||
for (final var runtimeEntry : runtimeSnapshot.entrySet()) {
|
||||
final var compilerId = compilerSnapshot.get(runtimeEntry.getKey());
|
||||
if (compilerId == null) {
|
||||
missingInCompiler.add(runtimeEntry.getKey());
|
||||
continue;
|
||||
}
|
||||
if (!runtimeEntry.getValue().equals(compilerId)) {
|
||||
mismatchedIds.add(runtimeEntry.getKey() + " runtime=" + runtimeEntry.getValue() + " compiler=" + compilerId);
|
||||
}
|
||||
}
|
||||
for (final var compilerEntry : compilerSnapshot.entrySet()) {
|
||||
if (!runtimeSnapshot.containsKey(compilerEntry.getKey())) {
|
||||
missingInRuntime.add(compilerEntry.getKey());
|
||||
}
|
||||
}
|
||||
return "intrinsic registry parity mismatch"
|
||||
+ "\nmissingInCompiler=" + List.copyOf(missingInCompiler)
|
||||
+ "\nmissingInRuntime=" + List.copyOf(missingInRuntime)
|
||||
+ "\nidMismatches=" + List.copyOf(mismatchedIds);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user