Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 75 additions & 1 deletion emulation/rocjitsu/docs/codegen.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,79 @@ while its installed dependencies come from the environment.
rocm-systems/shared/machine-readable-isa/isa/
```

## ISA additions

Repository-owned ISA additions can be supplied without modifying the public MR
ISA XML. Each ISA additions document has this shape:

```xml
<IsaAdditions Id="example-additions"
BaseArchitecture="AMD CDNA 5"
BaseSchemaVersion="1.2.0">
<EncodingIdentifierAdditions>
<EncodingIdentifierAddition>
<EncodingName>ENC_EXAMPLE</EncodingName>
<Opcode>7</Opcode>
<EncodingIdentifier Radix="2">...complete identifier...</EncodingIdentifier>
</EncodingIdentifierAddition>
</EncodingIdentifierAdditions>
<InstructionAdditions>
<Instruction>
<!-- One complete MR ISA <Instruction> node. -->
</Instruction>
</InstructionAdditions>
</IsaAdditions>
```

Apply one or more additions documents with a logical ISA name. Repeated
documents for one ISA are processed in command-line and document order:

```bash
python -m amdisa \
cdna5:/path/to/amdgpu_isa_cdna5.xml \
--isa-additions cdna5:/path/to/first-additions.xml \
--isa-additions cdna5:/path/to/second-additions.xml \
--isa-output /path/to/isa-output \
--dbt-output /path/to/dbt-output
```

The `EncodingIdentifierAdditions` element is optional. `InstructionAdditions`
is required and may be empty. The additions contract is intentionally narrower
than a general XML patch:

- Only complete `<Instruction>` nodes and complete identifiers for an existing
encoding's `<EncodingIdentifiers>` list are accepted. An additions document
cannot replace or delete base definitions, identifier masks, microcode
formats, encoding conditions, operand types, or data formats.
- Added instructions must reference encodings and operand types already
defined by the base XML.
- An identifier addition names its encoding and expected opcode. Its radix and
width must match the base encoding; its fixed bits must match an existing
identifier layout; and its decoded opcode must equal the declared opcode.
Duplicate and colliding decode slots are rejected. Each identifier must be
owned by an added instruction. An implied-literal identifier must have the
same instruction owner and opcode in its parent encoding.
- Additions document IDs and instruction names must be unique. Encoding/opcode
ownership collisions between different instructions, repeated instruction
forms, out-of-range opcodes, missing references, unknown root data, and base
architecture/schema mismatches are errors. Repetition of one instruction's
slot under distinct MR-ISA encoding conditions is allowed.
- Every input document and contained addition validates before the parser-owned
tree is changed. Validated identifiers are merged before normal
encoding/decode-table parsing, then instructions are appended in deterministic
file/document order. A bad later file cannot leave a partially merged
specification, and an active added instruction whose final opcode pointer
would remain unpopulated is an error.
- An empty additions document leaves generated output byte-identical to the base
XML.

The parsed `IsaSpec.applied_additions` and each added
`Instruction.source_addition` retain the source document's provenance for later
generator stages.
Provenance does not by itself define target availability: a separate variant
model must also be able to restrict encoding forms already present in the base
XML.

## Generated file locations

| Generated files | Location | Generator |
Expand Down Expand Up @@ -69,14 +142,15 @@ decode/encode functions and neutral field structs are auto-generated.
## CLI reference

```
python -m amdisa [--gen-isas] [--gen-dbt]
python -m amdisa [--isa-additions NAME:XML] [--gen-isas] [--gen-dbt]
[--isa-output DIR] [--include-root DIR]
[--dbt-output DIR] [NAME:]XML ...
```

| Option | Description |
|---|---|
| `[NAME:]XML ...` | One or more ISA XMLs. A recognized name selects its semantic profile. An unrecognized explicit name is treated as a custom generated identity, with its semantic profile detected from the XML |
| `--isa-additions NAME:XML` | Apply a validated ISA additions file to the named ISA; may be repeated |
| `--gen-isas` | Generate ISA C++ files (decoders, encodings, execute bodies) |
| `--gen-dbt` | Generate DBT legalization tables and encoding translators |
| `--isa-output DIR` | Output path for generated ISA C++ files |
Expand Down
5 changes: 5 additions & 0 deletions emulation/rocjitsu/lib/python/amdisa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
from amdisa.gpuisa import (
InstEncoding,
Instruction,
IsaAdditionProvenance,
IsaSpec,
Operand,
)
from amdisa.isa_additions import IsaAdditionError, apply_isa_additions
from amdisa.isa_profile import (
Cdna1Profile,
Cdna2Profile,
Expand Down Expand Up @@ -52,6 +54,8 @@
'Cdna5Profile',
'InstEncoding',
'Instruction',
'IsaAdditionError',
'IsaAdditionProvenance',
'IsaProfile',
'MemoryCoherencyModel',
'MnemonicRule',
Expand All @@ -63,6 +67,7 @@
'IsaSpec',
'Operand',
'Parser',
'apply_isa_additions',
'SchemaValueError',
'SchemaVersion',
'InstructionSemantics',
Expand Down
53 changes: 51 additions & 2 deletions emulation/rocjitsu/lib/python/amdisa/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,23 @@
}


def _group_isa_additions_args(entries: list[str] | None) -> dict[str, list[str]]:
"""Group repeated ``NAME:XML`` additions arguments by logical ISA name."""
grouped: dict[str, list[str]] = {}
for entry in entries or []:
if ':' not in entry:
raise ValueError(
f'--isa-additions entry must be name:xml_path, got: {entry}'
)
name, xml_path = entry.split(':', 1)
if not name or not xml_path:
raise ValueError(
f'--isa-additions entry must have non-empty name and path, got: {entry}'
)
grouped.setdefault(name, []).append(xml_path)
return grouped


def _collect_shared_execute_body_variants(specs, plan):
"""Collect candidate shared execute bodies from each ISA.

Expand Down Expand Up @@ -193,11 +210,35 @@ def _codegen_config(

def _run(args) -> None:
"""Parse the input XMLs and generate the requested outputs."""
specs = []
try:
addition_xmls = _group_isa_additions_args(getattr(args, 'isa_additions', None))
except ValueError as error:
print(f'error: {error}', file=sys.stderr)
sys.exit(1)

parsed_inputs = []
logical_names = set()
for entry in args.isafiles:
name, xml_path, profile_key = _parse_isa_arg(entry)
profile = _PROFILES[profile_key]()
spec = Parser(xml_path, profile).parse()
logical_name = (
name or profile.generated_arch_name or profile_key.replace('.', '_')
)
parsed_inputs.append((name, logical_name, xml_path, profile))
logical_names.add(logical_name)

unknown_addition_names = set(addition_xmls) - logical_names
if unknown_addition_names:
names = ', '.join(sorted(unknown_addition_names))
print(
f'error: --isa-additions names not present in ISA inputs: {names}',
file=sys.stderr,
)
sys.exit(1)

specs = []
for name, logical_name, xml_path, profile in parsed_inputs:
spec = Parser(xml_path, profile, addition_xmls.get(logical_name, ())).parse()
_apply_codegen_identity(spec, name)
sem = derive_all_semantics(spec)
specs.append((spec.arch_name, spec, sem))
Expand Down Expand Up @@ -312,6 +353,14 @@ def main() -> None:
help='Machine-readable AMD GPU ISA XML specifications. Each path may '
'have a generated identity prefix (e.g., cdna1:/path/to/cdna1.xml).',
)
arg_parser.add_argument(
'--isa-additions',
action='append',
default=[],
metavar='NAME:XML',
help='apply an ISA additions XML file to the named ISA. May be repeated; '
'files are applied in command-line order.',
)
arg_parser.add_argument(
'--gen-isas',
action='store_true',
Expand Down
15 changes: 4 additions & 11 deletions emulation/rocjitsu/lib/python/amdisa/codegen/_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
IsaSpec,
Operand,
OperandNamePattern,
opcode_constant_base_name,
opcode_name_fragment,
)
from amdisa.fieldless_policy import (
FieldlessCategory,
Expand Down Expand Up @@ -475,20 +477,11 @@ def _opcode_name_fragment(token: str) -> str:
meant to be read and used directly by handwritten DBT code, so split a
few packed ISA abbreviations that commonly appear inside one XML token.
"""
token = token.lower()
if token.endswith('exec') and len(token) > len('exec'):
return f'{token[:-4].capitalize()}Exec'
if token.endswith('pc') and len(token) > len('pc'):
return f'{token[:-2].capitalize()}Pc'
return token.capitalize()
return opcode_name_fragment(token)

@classmethod
def _opcode_const_base_name(cls, mnemonic: str) -> str:
return 'k' + ''.join(
cls._opcode_name_fragment(token)
for token in mnemonic.lower().split('_')
if token
)
return opcode_constant_base_name(mnemonic)

@staticmethod
def _emit_opcode_constant(name: str, value: int) -> str:
Expand Down
86 changes: 77 additions & 9 deletions emulation/rocjitsu/lib/python/amdisa/gpuisa.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,60 @@ class MicrocodeField:
}


def format_encoding_name(enc_name: str) -> str:
"""Format an MR-ISA encoding name as a C++ PascalCase fragment."""
parts = enc_name.split('_')
if parts[0] == 'ENC':
parts = parts[1:]
return ''.join(part.capitalize() for part in parts)


def format_true_encoding_name(
enc_name: str, *, is_implied_literal_enc: bool = False
) -> str:
"""Format the concrete C++ encoding class used by an instruction."""
if is_implied_literal_enc:
return enc_name.split('_')[0].capitalize()
return format_encoding_name(enc_name)


def format_instruction_name(
name: str, enc_name: str, *, is_implied_literal_enc: bool = False
) -> str:
"""Format an instruction's generated C++ class/decode-function name."""
mnemonic = ''.join(part.capitalize() for part in name.split('_'))
encoding = format_true_encoding_name(
enc_name, is_implied_literal_enc=is_implied_literal_enc
)
return f'{mnemonic}{encoding}'


def opcode_name_fragment(token: str) -> str:
"""Return one C++ opcode-constant fragment for a mnemonic token."""
token = token.lower()
if token.endswith('exec') and len(token) > len('exec'):
return f'{token[:-4].capitalize()}Exec'
if token.endswith('pc') and len(token) > len('pc'):
return f'{token[:-2].capitalize()}Pc'
return token.capitalize()


def opcode_constant_base_name(mnemonic: str) -> str:
"""Return the generated C++ opcode-constant base for a mnemonic."""
return 'k' + ''.join(
opcode_name_fragment(token) for token in mnemonic.lower().split('_') if token
)


def opcode_constant_name(
mnemonic: str, enc_name: str, *, is_implied_literal_enc: bool = False
) -> str:
"""Return the concrete encoding-qualified C++ opcode-constant name."""
return opcode_constant_base_name(mnemonic) + format_true_encoding_name(
enc_name, is_implied_literal_enc=is_implied_literal_enc
)


def synthesize_fieldless_name(operand_type: str) -> str:
"""Return a stable, valid C++ base identifier for a fieldless operand.

Expand Down Expand Up @@ -162,9 +216,7 @@ def __init__(self, enc_name: str, is_implied_literal_enc: bool = False) -> None:
@cached_property
def fmt_enc_name(self) -> str:
"""Encoding name formatted to C++ PascalCase style."""
if self.enc_name.split('_')[0] == 'ENC':
return ''.join(x.capitalize() for x in self.enc_name.split('_')[1:])
return ''.join(x.capitalize() for x in self.enc_name.split('_'))
return format_encoding_name(self.enc_name)

@cached_property
def fmt_true_enc_name(self) -> str:
Expand All @@ -174,9 +226,9 @@ def fmt_true_enc_name(self) -> str:
their parent encoding name (e.g., ``Vop2``) because the C++ class
hierarchy inherits from the parent encoding class.
"""
if self.is_implied_literal_enc:
return self.enc_name.split('_')[0].capitalize()
return self.fmt_enc_name
return format_true_encoding_name(
self.enc_name, is_implied_literal_enc=self.is_implied_literal_enc
)


class InstEncoding(InstBase):
Expand Down Expand Up @@ -243,6 +295,8 @@ class Instruction(InstBase):
classes. ``None`` means encoding provenance is unknown; modifier
generation rejects that state, so synthetic VOP instructions must
provide an explicit set.
source_addition: Additions provenance for a repository-supplied instruction,
or ``None`` when the instruction came from the base MR ISA XML.
"""

def __init__(
Expand All @@ -253,19 +307,22 @@ def __init__(
operands: list[Operand],
is_implied_literal_enc: bool = False,
available_encodings: frozenset[str] | None = None,
source_addition: IsaAdditionProvenance | None = None,
) -> None:
super().__init__(enc_name, is_implied_literal_enc)
self.name = name
self.opcode = opcode
self.operands = operands
self.available_encodings = available_encodings
self.source_addition = source_addition

@cached_property
def fmt_name(self) -> str:
"""Instruction name formatted to C++ PascalCase style."""
return (
f'{"".join(x.capitalize() for x in self.name.split("_"))}'
f'{self.fmt_true_enc_name}'
return format_instruction_name(
self.name,
self.enc_name,
is_implied_literal_enc=self.is_implied_literal_enc,
)

@cached_property
Expand Down Expand Up @@ -332,6 +389,14 @@ class DecodeTableEntry:
decode_func: str | None = None


@dataclass(frozen=True)
class IsaAdditionProvenance:
"""Identity and source path for one applied ISA additions document."""

identifier: str
path: str


class IsaSpec:
"""Internal representation of a machine-readable ISA spec.

Expand All @@ -357,6 +422,8 @@ class IsaSpec:
encoding conditions indicate an implied literal DWORD.
Populated during parsing using the profile's
``is_implied_literal_encoding()`` method.
applied_additions: Ordered provenance for ISA additions documents
merged into this specification.
"""

def __init__(
Expand All @@ -380,6 +447,7 @@ def __init__(
2, profile.max_enc_bits
)
self.alt_encs_with_implied_literal: set[str] = set()
self.applied_additions: tuple[IsaAdditionProvenance, ...] = ()
# Every fieldless operand type observed while parsing this spec.
# Consumed by fieldless_policy.validate_fieldless_taxonomy.
self.fieldless_operand_types: set[str] = set()
Expand Down
Loading
Loading