| #!/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:])) |