blob: 88bd8b5e3504dd58dbd92a67f13eba722dae7eff [file] [edit]
#!/usr/bin/env python3
# Copyright 2026 The IREE Authors
#
# Licensed under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Generates runtime VM ISA derived files."""
import argparse
import difflib
import json
import re
import sys
from pathlib import Path
_SET_ID_RE = re.compile(r"^[a-z][a-z0-9_]*$")
_C_TAG_RE = re.compile(r"^[A-Z][A-Z0-9_]*$")
_C_IDENTIFIER_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
_FIELD_RE = re.compile(r"^[a-z][a-z0-9_]*$")
_ENCODING_ID_RE = re.compile(r"^[a-z][a-z0-9_]*$")
_SYMBOL_RE = re.compile(r"^[A-Za-z][A-Za-z0-9]*$")
_MNEMONIC_RE = re.compile(r"^[a-z][a-z0-9_.]*$")
_INSTRUCTION_KINDS = frozenset(("instruction", "marker", "prefix"))
_REGISTER_BANKS = frozenset(("i32", "i64", "f32", "f64", "ref"))
def _format_opcode(opcode):
return f"0x{opcode:02X}"
def _c_enum_name(value):
return re.sub(r"[^A-Za-z0-9]+", "_", value).upper()
def _c_string_literal(value):
return json.dumps(value)
def _c_string_view(value):
return f"IREE_VM_ISA_SV({_c_string_literal(value)})"
def _feature_expr(feature):
if feature is None:
return "0"
return f"iree_vm_FeatureBits_{feature}"
def _fail(message):
raise ValueError(message)
def _require(condition, message):
if not condition:
_fail(message)
def _load_schema(schema_path):
try:
schema = json.loads(schema_path.read_text())
except json.JSONDecodeError as exc:
_fail(f"{schema_path}: invalid JSON: {exc}")
return schema
def _validate_bytecode_version(schema):
version = schema.get("bytecode_version")
_require(isinstance(version, dict), "bytecode_version must be an object")
for field_name in ("major", "minor"):
value = version.get(field_name)
_require(
isinstance(value, int) and value >= 0,
f"bytecode_version.{field_name} must be a non-negative integer",
)
def _validate_template_field(field, field_path, parameter_kinds):
_require(isinstance(field, dict), f"{field_path} must be an object")
name = field.get("name")
kind = field.get("kind")
_require(
isinstance(name, str) and _FIELD_RE.match(name),
f"{field_path}.name must be a lowercase identifier",
)
_require(
isinstance(kind, str) and _FIELD_RE.match(kind),
f"{field_path}.kind must be a lowercase identifier",
)
if kind == "register":
bank = field.get("bank")
_require(isinstance(bank, str), f"{field_path}.bank must be a string")
if bank.startswith("$"):
parameter_name = bank[1:]
_require(
parameter_name in parameter_kinds,
f"{field_path}.bank references unknown parameter '{parameter_name}'",
)
_require(
parameter_kinds[parameter_name] == "register_bank",
f"{field_path}.bank must reference a register_bank parameter",
)
else:
_require(
bank in _REGISTER_BANKS,
f"{field_path}.bank must be one of {sorted(_REGISTER_BANKS)}",
)
access = field.get("access")
_require(
access in ("read", "write"),
f"{field_path}.access must be 'read' or 'write'",
)
move = field.get("move")
_require(
move is None or move == "allow",
f"{field_path}.move must be 'allow' if present",
)
field_type = field.get("type")
_require(
field_type is None or isinstance(field_type, str),
f"{field_path}.type must be a string if present",
)
if isinstance(field_type, str) and not field_type.startswith("$"):
_require(
field_type == "any" or field_type in _REGISTER_BANKS,
f"{field_path}.type must be 'any' or one of " f"{sorted(_REGISTER_BANKS)}",
)
for key, value in field.items():
if not isinstance(value, str) or not value.startswith("$"):
continue
parameter_name = value[1:]
_require(
parameter_name in parameter_kinds,
f"{field_path}.{key} references unknown parameter '{parameter_name}'",
)
def _validate_operation_classes(schema):
operation_classes = schema.get("operation_classes", {})
_require(
isinstance(operation_classes, dict),
"operation_classes must be an object if present",
)
for class_name, operation_class in operation_classes.items():
class_path = f"operation_classes.{class_name}"
_require(
isinstance(class_name, str) and _FIELD_RE.match(class_name),
"operation class names must be lowercase identifiers",
)
_require(isinstance(operation_class, dict), f"{class_path} must be an object")
description = operation_class.get("description")
_require(
description is None or isinstance(description, str),
f"{class_path}.description must be a string if present",
)
parameters = operation_class.get("parameters", {})
_require(
isinstance(parameters, dict),
f"{class_path}.parameters must be an object",
)
parameter_kinds = {}
for parameter_name, parameter in parameters.items():
parameter_path = f"{class_path}.parameters.{parameter_name}"
_require(
isinstance(parameter_name, str) and _FIELD_RE.match(parameter_name),
f"{parameter_path} name must be a lowercase identifier",
)
_require(isinstance(parameter, dict), f"{parameter_path} must be an object")
kind = parameter.get("kind")
_require(
isinstance(kind, str) and _FIELD_RE.match(kind),
f"{parameter_path}.kind must be a lowercase identifier",
)
description = parameter.get("description")
_require(
description is None or isinstance(description, str),
f"{parameter_path}.description must be a string if present",
)
parameter_kinds[parameter_name] = kind
fields = operation_class.get("fields")
_require(isinstance(fields, list), f"{class_path}.fields must be an array")
_require(fields, f"{class_path}.fields must not be empty")
field_names = set()
for index, field in enumerate(fields):
field_path = f"{class_path}.fields[{index}]"
_validate_template_field(field, field_path, parameter_kinds)
field_name = field["name"]
_require(
field_name not in field_names,
f"duplicate field '{field_name}' in {class_path}",
)
field_names.add(field_name)
return operation_classes
def _validate_opcode_sets(schema):
opcode_sets = schema.get("opcode_sets")
_require(isinstance(opcode_sets, list), "opcode_sets must be an array")
_require(opcode_sets, "opcode_sets must not be empty")
seen_ids = set()
seen_tags = set()
seen_enum_names = set()
for index, opcode_set in enumerate(opcode_sets):
_require(
isinstance(opcode_set, dict), f"opcode_sets[{index}] must be an object"
)
opcode_set_id = opcode_set.get("id")
c_tag = opcode_set.get("c_tag")
enum_name = opcode_set.get("enum_name")
prefix = opcode_set.get("prefix")
feature = opcode_set.get("feature")
_require(
isinstance(opcode_set_id, str) and _SET_ID_RE.match(opcode_set_id),
f"opcode_sets[{index}].id must be a lowercase identifier",
)
_require(
opcode_set_id not in seen_ids,
f"duplicate opcode set id '{opcode_set_id}'",
)
seen_ids.add(opcode_set_id)
_require(
isinstance(c_tag, str) and _C_TAG_RE.match(c_tag),
f"opcode_sets[{index}].c_tag must be an uppercase C tag",
)
_require(c_tag not in seen_tags, f"duplicate opcode set C tag '{c_tag}'")
seen_tags.add(c_tag)
_require(
isinstance(enum_name, str) and _C_IDENTIFIER_RE.match(enum_name),
f"opcode_sets[{index}].enum_name must be a C identifier",
)
_require(
enum_name not in seen_enum_names,
f"duplicate opcode set enum name '{enum_name}'",
)
seen_enum_names.add(enum_name)
_require(
prefix is None or (isinstance(prefix, str) and _SYMBOL_RE.match(prefix)),
f"opcode_sets[{index}].prefix must be null or a VM opcode symbol",
)
_require(
feature is None or (isinstance(feature, str) and _C_TAG_RE.match(feature)),
f"opcode_sets[{index}].feature must be null or an uppercase C tag",
)
return opcode_sets
def _validate_encoding_spec(encoding, encoding_path, operation_classes):
_require(isinstance(encoding, dict), f"{encoding_path} must be an object")
encoding_class = encoding.get("class")
fields = encoding.get("fields")
_require(
(encoding_class is None) != (fields is None),
f"{encoding_path} must have exactly one of 'class' or 'fields'",
)
if encoding_class is not None:
_require(
isinstance(encoding_class, str) and encoding_class in operation_classes,
f"{encoding_path}.class must name an operation class",
)
parameters = encoding.get("parameters", {})
_require(
isinstance(parameters, dict),
f"{encoding_path}.parameters must be an object",
)
expected_parameters = operation_classes[encoding_class].get("parameters", {})
actual_parameters = set(parameters)
missing_parameters = sorted(set(expected_parameters) - actual_parameters)
extra_parameters = sorted(actual_parameters - set(expected_parameters))
_require(
not missing_parameters,
f"{encoding_path}.parameters missing {missing_parameters}",
)
_require(
not extra_parameters,
f"{encoding_path}.parameters has unknown {extra_parameters}",
)
for parameter_name, parameter_value in parameters.items():
_require(
isinstance(parameter_value, str) and _FIELD_RE.match(parameter_value),
f"{encoding_path}.parameters.{parameter_name} must be "
"a lowercase identifier",
)
parameter_kind = expected_parameters[parameter_name]["kind"]
if parameter_kind == "register_bank":
_require(
parameter_value in _REGISTER_BANKS,
f"{encoding_path}.parameters.{parameter_name} must be one "
f"of {sorted(_REGISTER_BANKS)}",
)
return
_require(
isinstance(fields, list),
f"{encoding_path}.fields must be an array",
)
_require(fields, f"{encoding_path}.fields must not be empty")
field_names = set()
for index, field in enumerate(fields):
field_path = f"{encoding_path}.fields[{index}]"
_validate_template_field(field, field_path, {})
field_name = field["name"]
_require(
field_name not in field_names,
f"duplicate field '{field_name}' in {encoding_path}",
)
field_names.add(field_name)
def _validate_encodings(schema, operation_classes):
encodings = schema.get("encodings", {})
_require(isinstance(encodings, dict), "encodings must be an object if present")
for encoding_id, encoding in encodings.items():
_require(
isinstance(encoding_id, str) and _ENCODING_ID_RE.match(encoding_id),
"encoding ids must be lowercase identifiers",
)
_validate_encoding_spec(encoding, f"encodings.{encoding_id}", operation_classes)
return encodings
def _validate_instruction_encoding(
instruction, instruction_path, operation_classes, encodings
):
encoding = instruction.get("encoding")
if encoding is None:
return None
if isinstance(encoding, str):
_require(
encoding in encodings,
f"{instruction_path}.encoding references unknown encoding '{encoding}'",
)
instruction["encoding"] = encodings[encoding]
instruction["encoding_ref"] = encoding
return encoding
_validate_encoding_spec(encoding, f"{instruction_path}.encoding", operation_classes)
return None
def _validate_instructions(schema, opcode_sets, operation_classes, encodings):
opcode_set_ids = {opcode_set["id"] for opcode_set in opcode_sets}
instructions = schema.get("instructions")
_require(isinstance(instructions, list), "instructions must be an array")
instructions_by_set = {opcode_set_id: {} for opcode_set_id in opcode_set_ids}
symbols_by_set = {opcode_set_id: set() for opcode_set_id in opcode_set_ids}
mnemonics = set()
used_encodings = set()
for index, instruction in enumerate(instructions):
instruction_path = f"instructions[{index}]"
_require(isinstance(instruction, dict), f"{instruction_path} must be an object")
opcode_set_id = instruction.get("opcode_set")
_require(
isinstance(opcode_set_id, str) and opcode_set_id in opcode_set_ids,
f"{instruction_path}.opcode_set references an unknown opcode set",
)
opcode = instruction.get("opcode")
_require(
isinstance(opcode, int) and 0 <= opcode <= 0xFF,
f"{instruction_path}.opcode must be an integer in [0, 255]",
)
_require(
opcode not in instructions_by_set[opcode_set_id],
f"duplicate opcode {_format_opcode(opcode)} in opcode set '{opcode_set_id}'",
)
symbol = instruction.get("symbol")
_require(
isinstance(symbol, str) and _SYMBOL_RE.match(symbol),
f"{instruction_path}.symbol must be a VM opcode symbol",
)
_require(
symbol not in symbols_by_set[opcode_set_id],
f"duplicate symbol '{symbol}' in opcode set '{opcode_set_id}'",
)
symbols_by_set[opcode_set_id].add(symbol)
kind = instruction.get("kind", "instruction")
_require(
kind in _INSTRUCTION_KINDS,
f"{instruction_path}.kind must be one of {sorted(_INSTRUCTION_KINDS)}",
)
mnemonic = instruction.get("mnemonic")
_require(
mnemonic is None
or (isinstance(mnemonic, str) and _MNEMONIC_RE.match(mnemonic)),
f"{instruction_path}.mnemonic must be null or a lowercase dotted name",
)
if kind == "prefix":
target_opcode_set = instruction.get("target_opcode_set")
_require(
isinstance(target_opcode_set, str)
and target_opcode_set in opcode_set_ids,
f"{instruction_path}.target_opcode_set must name an opcode set",
)
has_encoding = instruction.get("encoding") is not None
encoding_ref = _validate_instruction_encoding(
instruction, instruction_path, operation_classes, encodings
)
if has_encoding:
_require(
kind == "instruction",
f"{instruction_path}.encoding is only valid on instructions",
)
_require(
mnemonic is not None,
f"{instruction_path}.mnemonic is required for encoded instructions",
)
_require(
mnemonic not in mnemonics,
f"duplicate instruction mnemonic '{mnemonic}'",
)
mnemonics.add(mnemonic)
if encoding_ref is not None:
used_encodings.add(encoding_ref)
instructions_by_set[opcode_set_id][opcode] = instruction
unused_encodings = sorted(set(encodings) - used_encodings)
_require(not unused_encodings, f"unused encodings: {unused_encodings}")
return instructions_by_set
def _validate_opcode_set_prefixes(opcode_sets, instructions_by_set):
core_instructions = instructions_by_set.get("core", {})
core_prefixes_by_symbol = {
instruction["symbol"]: instruction
for instruction in core_instructions.values()
if instruction.get("kind") == "prefix"
}
for opcode_set in opcode_sets:
prefix = opcode_set.get("prefix")
if prefix is None:
continue
_require(
prefix in core_prefixes_by_symbol,
f"opcode set '{opcode_set['id']}' prefix '{prefix}' must name "
"a core prefix instruction",
)
prefix_instruction = core_prefixes_by_symbol[prefix]
_require(
prefix_instruction.get("target_opcode_set") == opcode_set["id"],
f"opcode set '{opcode_set['id']}' prefix '{prefix}' targets "
f"'{prefix_instruction.get('target_opcode_set')}'",
)
def _load_and_validate_schema(schema_path):
schema = _load_schema(schema_path)
_validate_bytecode_version(schema)
operation_classes = _validate_operation_classes(schema)
encodings = _validate_encodings(schema, operation_classes)
opcode_sets = _validate_opcode_sets(schema)
instructions_by_set = _validate_instructions(
schema, opcode_sets, operation_classes, encodings
)
_validate_opcode_set_prefixes(opcode_sets, instructions_by_set)
return opcode_sets, instructions_by_set
def _resolve_parameter(value, parameters):
if isinstance(value, str) and value.startswith("$"):
return parameters[value[1:]]
return value
def _resolve_encoding_fields(encoding, operation_classes):
if "class" in encoding:
parameters = encoding.get("parameters", {})
fields = operation_classes[encoding["class"]]["fields"]
else:
parameters = {}
fields = encoding["fields"]
resolved_fields = []
for field in fields:
resolved_field = {
key: _resolve_parameter(value, parameters) for key, value in field.items()
}
resolved_fields.append(resolved_field)
return resolved_fields
def _encoded_instructions(opcode_sets, instructions_by_set):
for opcode_set in opcode_sets:
opcode_set_id = opcode_set["id"]
for opcode in range(256):
instruction = instructions_by_set[opcode_set_id].get(opcode)
if instruction and "encoding" in instruction:
yield opcode_set, instruction
def _generate_encoding_table_header(schema_path):
schema = _load_schema(schema_path)
_validate_bytecode_version(schema)
operation_classes = _validate_operation_classes(schema)
encodings = _validate_encodings(schema, operation_classes)
opcode_sets = _validate_opcode_sets(schema)
instructions_by_set = _validate_instructions(
schema, opcode_sets, operation_classes, encodings
)
_validate_opcode_set_prefixes(opcode_sets, instructions_by_set)
resolved_field_kinds = set()
for _, instruction in _encoded_instructions(opcode_sets, instructions_by_set):
resolved_field_kinds.update(
field["kind"]
for field in _resolve_encoding_fields(
instruction["encoding"], operation_classes
)
)
lines = [
"/*===- VM ISA generated file ------------------------------*- C -*-===*\\",
"|* *|",
"|* IREE VM Encoding Tables *|",
"|* *|",
"|* Generated by build_tools/scripts/generate_vm_isa.py from *|",
"|* runtime/src/iree/vm/bytecode/isa/isa.json. *|",
"|* *|",
"|* Automatically generated file, do not edit! *|",
"|* *|",
"\\*===----------------------------------------------------------------------===*/",
"",
"#ifndef IREE_VM_BYTECODE_ISA_ENCODING_TABLE_H_",
"#define IREE_VM_BYTECODE_ISA_ENCODING_TABLE_H_",
"",
'#include "iree/vm/bytecode/isa/isa.h"',
"",
"#ifdef __cplusplus",
'extern "C" {',
"#endif // __cplusplus",
"",
"typedef enum iree_vm_isa_opcode_set_e {",
]
for index, opcode_set in enumerate(opcode_sets):
lines.append(
f" IREE_VM_ISA_OPCODE_SET_{_c_enum_name(opcode_set['id'])} = {index},"
)
lines.extend(
[
f" IREE_VM_ISA_OPCODE_SET_COUNT = {len(opcode_sets)},",
"} iree_vm_isa_opcode_set_t;",
"",
"typedef enum iree_vm_isa_field_kind_e {",
]
)
for index, kind in enumerate(sorted(resolved_field_kinds)):
lines.append(f" IREE_VM_ISA_FIELD_KIND_{_c_enum_name(kind)} = {index},")
lines.extend(
[
"} iree_vm_isa_field_kind_t;",
"",
"typedef enum iree_vm_isa_register_bank_e {",
" IREE_VM_ISA_REGISTER_BANK_NONE = 0,",
]
)
for bank in sorted(_REGISTER_BANKS):
lines.append(f" IREE_VM_ISA_REGISTER_BANK_{_c_enum_name(bank)},")
lines.extend(
[
"} iree_vm_isa_register_bank_t;",
"",
"typedef enum iree_vm_isa_value_type_e {",
" IREE_VM_ISA_VALUE_TYPE_NONE = 0,",
" IREE_VM_ISA_VALUE_TYPE_ANY,",
]
)
for value_type in sorted(_REGISTER_BANKS):
lines.append(f" IREE_VM_ISA_VALUE_TYPE_{_c_enum_name(value_type)},")
lines.extend(
[
"} iree_vm_isa_value_type_t;",
"",
"typedef enum iree_vm_isa_field_access_e {",
" IREE_VM_ISA_FIELD_ACCESS_NONE = 0,",
" IREE_VM_ISA_FIELD_ACCESS_READ,",
" IREE_VM_ISA_FIELD_ACCESS_WRITE,",
"} iree_vm_isa_field_access_t;",
"",
"typedef struct iree_vm_isa_opcode_set_descriptor_t {",
" // Schema identifier for this opcode set.",
" iree_string_view_t id;",
" // Core opcode symbol used as the extension prefix, if any.",
" iree_string_view_t prefix_symbol;",
" // Core opcode byte used as the extension prefix, or 0 for core.",
" uint8_t prefix_opcode;",
" // Feature bits required by instructions in this opcode set.",
" iree_vm_FeatureBits_enum_t required_features;",
"} iree_vm_isa_opcode_set_descriptor_t;",
"",
"typedef struct iree_vm_isa_field_t {",
" // Field name from the ISA schema.",
" iree_string_view_t name;",
" // Field encoder/decoder category.",
" iree_vm_isa_field_kind_t kind;",
" // Register bank for register fields, or NONE otherwise.",
" iree_vm_isa_register_bank_t register_bank;",
" // Value type for attributes and variadic fields.",
" iree_vm_isa_value_type_t value_type;",
" // Register access mode for register fields.",
" iree_vm_isa_field_access_t access;",
" // Whether ref register fields may use MOVE semantics.",
" bool allows_move;",
"} iree_vm_isa_field_t;",
"",
"typedef struct iree_vm_isa_instruction_t {",
" // Textual mnemonic used by VM assembly.",
" iree_string_view_t mnemonic;",
" // C opcode symbol without the IREE_VM_OP_* prefix.",
" iree_string_view_t symbol;",
" // Named encoding referenced by the instruction.",
" iree_string_view_t encoding;",
" // Opcode set containing this instruction.",
" iree_vm_isa_opcode_set_t opcode_set;",
" // Opcode byte within the opcode set.",
" uint8_t opcode;",
" // Feature bits required by this instruction.",
" iree_vm_FeatureBits_enum_t required_features;",
" // Number of encoded fields following the opcode byte.",
" iree_host_size_t field_count;",
" // Ordered encoded fields following the opcode byte.",
" const iree_vm_isa_field_t* fields;",
"} iree_vm_isa_instruction_t;",
"",
"IREE_API_EXPORT const iree_vm_isa_opcode_set_descriptor_t*",
"iree_vm_isa_opcode_set_descriptor(iree_vm_isa_opcode_set_t opcode_set);",
"",
"IREE_API_EXPORT iree_host_size_t iree_vm_isa_instruction_count(void);",
"",
"IREE_API_EXPORT const iree_vm_isa_instruction_t*",
"iree_vm_isa_instruction_table(void);",
"",
"IREE_API_EXPORT const iree_vm_isa_instruction_t*",
"iree_vm_isa_lookup_mnemonic(iree_string_view_t mnemonic);",
"",
"IREE_API_EXPORT const iree_vm_isa_instruction_t*",
"iree_vm_isa_lookup_opcode(iree_vm_isa_opcode_set_t opcode_set,",
" uint8_t opcode);",
"",
"#ifdef __cplusplus",
'} // extern "C"',
"#endif // __cplusplus",
"",
"#endif // IREE_VM_BYTECODE_ISA_ENCODING_TABLE_H_",
"",
]
)
return "\n".join(lines)
def _value_type_enum(value):
if value is None:
return "IREE_VM_ISA_VALUE_TYPE_NONE"
if value == "any":
return "IREE_VM_ISA_VALUE_TYPE_ANY"
return f"IREE_VM_ISA_VALUE_TYPE_{_c_enum_name(value)}"
def _register_bank_enum(value):
if value is None:
return "IREE_VM_ISA_REGISTER_BANK_NONE"
return f"IREE_VM_ISA_REGISTER_BANK_{_c_enum_name(value)}"
def _field_access_enum(value):
if value is None:
return "IREE_VM_ISA_FIELD_ACCESS_NONE"
return f"IREE_VM_ISA_FIELD_ACCESS_{_c_enum_name(value)}"
def _field_kind_enum(value):
return f"IREE_VM_ISA_FIELD_KIND_{_c_enum_name(value)}"
def _opcode_set_enum(value):
return f"IREE_VM_ISA_OPCODE_SET_{_c_enum_name(value)}"
def _find_core_prefix_instruction(opcode_set, instructions_by_set):
prefix = opcode_set.get("prefix")
if prefix is None:
return None
for instruction in instructions_by_set["core"].values():
if instruction["symbol"] == prefix:
return instruction
_fail(f"opcode set '{opcode_set['id']}' has no resolved prefix")
def _generate_encoding_table_source(schema_path):
schema = _load_schema(schema_path)
_validate_bytecode_version(schema)
operation_classes = _validate_operation_classes(schema)
encodings = _validate_encodings(schema, operation_classes)
opcode_sets = _validate_opcode_sets(schema)
instructions_by_set = _validate_instructions(
schema, opcode_sets, operation_classes, encodings
)
_validate_opcode_set_prefixes(opcode_sets, instructions_by_set)
used_encodings = []
seen_encodings = set()
for _, instruction in _encoded_instructions(opcode_sets, instructions_by_set):
encoding_ref = instruction["encoding_ref"]
if encoding_ref not in seen_encodings:
used_encodings.append(encoding_ref)
seen_encodings.add(encoding_ref)
lines = [
"/*===- VM ISA generated file ------------------------------*- C -*-===*\\",
"|* *|",
"|* IREE VM Encoding Tables *|",
"|* *|",
"|* Generated by build_tools/scripts/generate_vm_isa.py from *|",
"|* runtime/src/iree/vm/bytecode/isa/isa.json. *|",
"|* *|",
"|* Automatically generated file, do not edit! *|",
"|* *|",
"\\*===----------------------------------------------------------------------===*/",
"",
'#include "iree/vm/bytecode/isa/encoding_table.h"',
"",
"#define IREE_VM_ISA_SV(literal) {literal, sizeof(literal) - 1}",
"",
"static const iree_vm_isa_opcode_set_descriptor_t",
" iree_vm_isa_opcode_set_descriptors[] = {",
]
for opcode_set in opcode_sets:
prefix_instruction = _find_core_prefix_instruction(
opcode_set, instructions_by_set
)
prefix_opcode = (
_format_opcode(prefix_instruction["opcode"]) if prefix_instruction else "0"
)
prefix_symbol = opcode_set.get("prefix") or ""
lines.extend(
[
" {",
f" .id = {_c_string_view(opcode_set['id'])},",
f" .prefix_symbol = {_c_string_view(prefix_symbol)},",
f" .prefix_opcode = {prefix_opcode},",
f" .required_features = {_feature_expr(opcode_set.get('feature'))},",
" },",
]
)
lines.extend(["};", ""])
for encoding_ref in used_encodings:
fields = _resolve_encoding_fields(encodings[encoding_ref], operation_classes)
lines.append(
f"static const iree_vm_isa_field_t "
f"iree_vm_isa_{encoding_ref}_fields[] = {{"
)
for field in fields:
lines.extend(
[
" {",
f" .name = {_c_string_view(field['name'])},",
f" .kind = {_field_kind_enum(field['kind'])},",
f" .register_bank = {_register_bank_enum(field.get('bank'))},",
f" .value_type = {_value_type_enum(field.get('type'))},",
f" .access = {_field_access_enum(field.get('access'))},",
" .allows_move = "
f"{'true' if field.get('move') == 'allow' else 'false'},",
" },",
]
)
lines.extend(["};", ""])
lines.append(
"static const iree_vm_isa_instruction_t iree_vm_isa_instructions[] = {"
)
for opcode_set, instruction in _encoded_instructions(
opcode_sets, instructions_by_set
):
encoding_ref = instruction["encoding_ref"]
lines.extend(
[
" {",
f" .mnemonic = {_c_string_view(instruction['mnemonic'])},",
f" .symbol = {_c_string_view(instruction['symbol'])},",
f" .encoding = {_c_string_view(encoding_ref)},",
f" .opcode_set = {_opcode_set_enum(opcode_set['id'])},",
f" .opcode = {_format_opcode(instruction['opcode'])},",
f" .required_features = {_feature_expr(opcode_set.get('feature'))},",
" .field_count = "
f"IREE_ARRAYSIZE(iree_vm_isa_{encoding_ref}_fields),",
f" .fields = iree_vm_isa_{encoding_ref}_fields,",
" },",
]
)
lines.extend(
[
"};",
"",
"IREE_API_EXPORT const iree_vm_isa_opcode_set_descriptor_t*",
"iree_vm_isa_opcode_set_descriptor(iree_vm_isa_opcode_set_t opcode_set) {",
" if ((uint32_t)opcode_set >= IREE_VM_ISA_OPCODE_SET_COUNT) {",
" return NULL;",
" }",
" return &iree_vm_isa_opcode_set_descriptors[opcode_set];",
"}",
"",
"IREE_API_EXPORT iree_host_size_t iree_vm_isa_instruction_count(void) {",
" return IREE_ARRAYSIZE(iree_vm_isa_instructions);",
"}",
"",
"IREE_API_EXPORT const iree_vm_isa_instruction_t*",
"iree_vm_isa_instruction_table(void) {",
" return iree_vm_isa_instructions;",
"}",
"",
"IREE_API_EXPORT const iree_vm_isa_instruction_t*",
"iree_vm_isa_lookup_mnemonic(iree_string_view_t mnemonic) {",
" for (iree_host_size_t i = 0; i < IREE_ARRAYSIZE(iree_vm_isa_instructions);",
" ++i) {",
" const iree_vm_isa_instruction_t* instruction =",
" &iree_vm_isa_instructions[i];",
" if (iree_string_view_equal(instruction->mnemonic, mnemonic)) {",
" return instruction;",
" }",
" }",
" return NULL;",
"}",
"",
"IREE_API_EXPORT const iree_vm_isa_instruction_t*",
"iree_vm_isa_lookup_opcode(iree_vm_isa_opcode_set_t opcode_set,",
" uint8_t opcode) {",
" for (iree_host_size_t i = 0; i < IREE_ARRAYSIZE(iree_vm_isa_instructions);",
" ++i) {",
" const iree_vm_isa_instruction_t* instruction =",
" &iree_vm_isa_instructions[i];",
" if (instruction->opcode_set == opcode_set && instruction->opcode == opcode) {",
" return instruction;",
" }",
" }",
" return NULL;",
"}",
"",
]
)
return "\n".join(lines)
def _generate_op_table(schema_path):
opcode_sets, instructions_by_set = _load_and_validate_schema(schema_path)
lines = [
"/*===- VM ISA generated file ------------------------------*- C -*-===*\\",
"|* *|",
"|* IREE VM Operation Tables *|",
"|* *|",
"|* Generated by build_tools/scripts/generate_vm_isa.py from *|",
"|* runtime/src/iree/vm/bytecode/isa/isa.json. *|",
"|* *|",
"|* Automatically generated file, do not edit! *|",
"|* *|",
"\\*===----------------------------------------------------------------------===*/",
"",
]
for opcode_set in opcode_sets:
c_tag = opcode_set["c_tag"]
instructions = instructions_by_set[opcode_set["id"]]
lines.append("typedef enum {")
for opcode in range(256):
instruction = instructions.get(opcode)
if instruction:
lines.append(
f" IREE_VM_OP_{c_tag}_{instruction['symbol']} = "
f"{_format_opcode(opcode)},"
)
else:
lines.append(f" IREE_VM_OP_{c_tag}_RSV_{_format_opcode(opcode)},")
lines.append(f"}} {opcode_set['enum_name']};")
lines.append("")
lines.append(f"#define IREE_VM_OP_{c_tag}_TABLE(OPC, RSV) \\")
for opcode in range(256):
instruction = instructions.get(opcode)
suffix = " \\" if opcode != 0xFF else ""
if instruction:
lines.append(
f" OPC({_format_opcode(opcode)}, {instruction['symbol']}){suffix}"
)
else:
lines.append(f" RSV({_format_opcode(opcode)}){suffix}")
lines.append("")
lines.append("")
return "\n".join(lines) + "\n"
def _check_output(output_path, generated_output):
existing_output = output_path.read_text()
if existing_output == generated_output:
return 0
diff = difflib.unified_diff(
existing_output.splitlines(keepends=True),
generated_output.splitlines(keepends=True),
fromfile=str(output_path),
tofile=f"{output_path} (generated)",
)
sys.stderr.writelines(diff)
return 1
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--schema",
type=Path,
required=True,
help="Path to runtime/src/iree/vm/bytecode/isa/isa.json.",
)
parser.add_argument(
"--op-table",
type=Path,
help="Path to write or check op_table.h.",
)
parser.add_argument(
"--encoding-table-header",
type=Path,
help="Path to write encoding_table.h.",
)
parser.add_argument(
"--encoding-table-source",
type=Path,
help="Path to write encoding_table.c.",
)
parser.add_argument(
"--check",
action="store_true",
help="Checks that the output path already contains the generated output.",
)
parser.add_argument(
"--stdout",
action="store_true",
help="Writes the generated op table to stdout.",
)
args = parser.parse_args(argv)
try:
outputs = []
if args.op_table is not None:
outputs.append((args.op_table, _generate_op_table(args.schema)))
if args.encoding_table_header is not None:
outputs.append(
(
args.encoding_table_header,
_generate_encoding_table_header(args.schema),
)
)
if args.encoding_table_source is not None:
outputs.append(
(
args.encoding_table_source,
_generate_encoding_table_source(args.schema),
)
)
if args.stdout:
sys.stdout.write(_generate_op_table(args.schema))
return 0
if not outputs:
parser.error(
"at least one output path is required unless --stdout is specified"
)
if args.check:
status = 0
for output_path, generated_output in outputs:
status |= _check_output(output_path, generated_output)
return status
for output_path, generated_output in outputs:
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(generated_output)
return 0
except ValueError as exc:
print(f"error: {exc}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))