from __future__ import annotations
import argparse
import pathlib
import re
import sys
ROOT = pathlib.Path(__file__).resolve().parent
MANIFEST = ROOT / "opcode-manifest.tsv"
RUST_OUTPUT = ROOT / "src" / "opcode_generated.rs"
DOC_OUTPUT = ROOT / "OPCODES.md"
FIELDS = ("value", "mnemonic", "identity", "operands", "width", "control", "constant_pool", "since", "until")
def read_manifest(path: pathlib.Path) -> list[dict[str, str]]:
lines = [line for line in path.read_text(encoding="utf-8").splitlines() if line and not line.startswith("#")]
if not lines or tuple(lines[0].split("\t")) != FIELDS:
raise ValueError(f"{path}: expected tab-separated header {' '.join(FIELDS)}")
rows: list[dict[str, str]] = []
for line_number, line in enumerate(lines[1:], 2):
values = line.split("\t")
if len(values) != len(FIELDS):
raise ValueError(f"{path}:{line_number}: expected {len(FIELDS)} fields, found {len(values)}")
row = dict(zip(FIELDS, values))
try:
value = int(row["value"], 16)
except ValueError as error:
raise ValueError(f"{path}:{line_number}: invalid opcode value {row['value']!r}") from error
if not 0 <= value <= 0xFF:
raise ValueError(f"{path}:{line_number}: opcode value is outside one byte")
row["numeric_value"] = str(value)
rows.append(row)
values = [int(row["numeric_value"]) for row in rows]
if values != list(range(256)):
raise ValueError(f"{path}: values must cover 0x00..0xff exactly once in ascending order")
identities = [row["identity"] for row in rows]
if len(identities) != len(set(identities)) or any(not re.fullmatch(r"[A-Z][A-Za-z0-9]*", item) for item in identities):
raise ValueError(f"{path}: identities must be unique Rust UpperCamelCase identifiers")
return rows
def rust_string(value: str) -> str:
return '"' + value.replace('\\', '\\\\').replace('"', '\\"') + '"'
def rust_opcode_variant(row: dict[str, str]) -> str:
documentation = f"JVM opcode `{row['mnemonic']}` (`{row['value']}`)."
return (
f" #[doc = {rust_string(documentation)}] "
f"{row['identity']} = {row['value']},"
)
def generate_rust(rows: list[dict[str, str]]) -> str:
variants = "\n".join(rust_opcode_variant(row) for row in rows)
metadata = "\n".join(
" OpcodeMetadata { "
f"opcode: Opcode::{row['identity']}, mnemonic: {rust_string(row['mnemonic'])}, "
f"operands: {rust_string(row['operands'])}, width: {rust_string(row['width'])}, "
f"control: {rust_string(row['control'])}, constant_pool: {rust_string(row['constant_pool'])}, "
f"since: {rust_string(row['since'])}, until: {rust_string(row['until'])} "
"},"
for row in rows
)
return f'''// @generated by generate_opcodes.py from opcode-manifest.tsv; DO NOT EDIT.
/// Stable identity for every byte in the JVM opcode space.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(u8)]
#[rustfmt::skip]
pub enum Opcode {{
{variants}
}}
/// Generated metadata for one JVM opcode byte.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct OpcodeMetadata {{
/// Stable opcode identity.
pub opcode: Opcode,
/// JVM mnemonic, or a stable `reserved_*` name for an unassigned byte.
pub mnemonic: &'static str,
/// Declarative operand layout.
pub operands: &'static str,
/// Encoded width in bytes, or `variable`/`invalid`.
pub width: &'static str,
/// Control-flow category.
pub control: &'static str,
/// Admitted constant-pool category, or `none`.
pub constant_pool: &'static str,
/// Earliest admitted classfile version.
pub since: &'static str,
/// Last admitted classfile version, or `unbounded`.
pub until: &'static str,
}}
/// Complete byte-indexed opcode metadata table.
#[rustfmt::skip]
pub static OPCODES: [OpcodeMetadata; 256] = [
{metadata}
];
impl Opcode {{
/// Returns the identity for `byte`, including reserved identities.
#[must_use]
pub const fn from_byte(byte: u8) -> Self {{
OPCODES[byte as usize].opcode
}}
/// Returns this opcode's generated metadata.
#[must_use]
pub const fn metadata(self) -> &'static OpcodeMetadata {{
&OPCODES[self as usize]
}}
}}
'''
def generate_docs(rows: list[dict[str, str]]) -> str:
body = "\n".join(
f"| `{row['value']}` | `{row['mnemonic']}` | `{row['operands']}` | {row['width']} | {row['control']} | `{row['constant_pool']}` | {row['since']} | {row['until']} |"
for row in rows
)
return """<!-- @generated by generate_opcodes.py from opcode-manifest.tsv; DO NOT EDIT. -->
# JVM opcode manifest
This reference is generated from the crate's sole opcode inventory.
| Byte | Mnemonic | Operand layout | Width | Control | Constant pool | Since | Until |
|---|---|---|---:|---|---|---|---|
""" + body + "\n"
def find_parallel_inventories(root: pathlib.Path) -> list[str]:
findings: list[str] = []
known = {row["mnemonic"] for row in read_manifest(root / "opcode-manifest.tsv") if not row["mnemonic"].startswith("reserved_")}
source_suffixes = {".rs", ".toml", ".json", ".csv", ".tsv", ".yaml", ".yml"}
allowed = {root / "opcode-manifest.tsv", root / "src" / "opcode_generated.rs"}
for path in sorted(item for item in root.rglob("*") if item.suffix in source_suffixes):
if path in allowed or "target" in path.parts:
continue
text = path.read_text(encoding="utf-8")
byte_facts = set(re.findall(r"(?<![A-Za-z0-9_])0x([0-9a-fA-F]{2})(?![A-Za-z0-9_])", text))
mnemonic_facts = {name for name in known if re.search(rf'\b{re.escape(name)}\b', text)}
opcode_named_bytes = any(
len(set(re.findall(r"(?<![A-Za-z0-9_])0x([0-9a-fA-F]{2})(?![A-Za-z0-9_])", declaration))) >= 8
for declaration in re.findall(
r"(?is)(?:const|static)\s+[A-Za-z0-9_]*(?:opcode|instruction)[A-Za-z0-9_]*.*?=.*?;",
text,
)
)
if len(mnemonic_facts) >= 8 or opcode_named_bytes:
findings.append(f"{path.relative_to(root)} contains a parallel opcode-like inventory ({len(byte_facts)} bytes, {len(mnemonic_facts)} mnemonics)")
return findings
def write_or_check(path: pathlib.Path, expected: str, check: bool) -> bool:
if check:
return path.exists() and path.read_text(encoding="utf-8") == expected
path.write_text(expected, encoding="utf-8")
return True
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--check", action="store_true", help="fail unless generated outputs are current")
parser.add_argument("--scan", action="store_true", help="also reject parallel Rust opcode inventories")
args = parser.parse_args()
try:
rows = read_manifest(MANIFEST)
current = write_or_check(RUST_OUTPUT, generate_rust(rows), args.check)
current &= write_or_check(DOC_OUTPUT, generate_docs(rows), args.check)
findings = find_parallel_inventories(ROOT) if args.scan else []
except (OSError, ValueError) as error:
print(error, file=sys.stderr)
return 1
if not current:
print("generated opcode files are stale; run ./generate_opcodes.py", file=sys.stderr)
for finding in findings:
print(finding, file=sys.stderr)
return 0 if current and not findings else 1
if __name__ == "__main__":
raise SystemExit(main())