simdref 0.0.13__tar.gz → 0.0.15__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {simdref-0.0.13/src/simdref.egg-info → simdref-0.0.15}/PKG-INFO +3 -2
- {simdref-0.0.13 → simdref-0.0.15}/README.md +1 -1
- {simdref-0.0.13 → simdref-0.0.15}/pyproject.toml +6 -1
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/annotate.py +71 -16
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/arm_instructions.py +92 -11
- simdref-0.0.15/src/simdref/asm_identity.py +928 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/cli.py +32 -1
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/display.py +27 -5
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/export.py +65 -2
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/ingest_catalog.py +110 -18
- simdref-0.0.15/src/simdref/lsp.py +1608 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/models.py +18 -4
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/perf.py +22 -2
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/riscv.py +77 -33
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/storage.py +41 -12
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/tui.py +1 -1
- {simdref-0.0.13 → simdref-0.0.15/src/simdref.egg-info}/PKG-INFO +3 -2
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref.egg-info/SOURCES.txt +16 -0
- simdref-0.0.15/src/simdref.egg-info/requires.txt +13 -0
- simdref-0.0.15/tests/test_asm_identity.py +1260 -0
- simdref-0.0.15/tests/test_asm_wire.py +173 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_auto_update_on_version_change.py +5 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_display.py +66 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_export.py +101 -0
- simdref-0.0.15/tests/test_ingest_enc_fields.py +374 -0
- simdref-0.0.15/tests/test_leftover_arm_form_pick.py +276 -0
- simdref-0.0.15/tests/test_leftover_arm_postindex.py +17 -0
- simdref-0.0.15/tests/test_leftover_att_movx.py +74 -0
- simdref-0.0.15/tests/test_leftover_autoupdate.py +23 -0
- simdref-0.0.15/tests/test_leftover_catalog_gaps.py +277 -0
- simdref-0.0.15/tests/test_leftover_ingest_isa_merge.py +168 -0
- simdref-0.0.15/tests/test_leftover_intel_size.py +103 -0
- simdref-0.0.15/tests/test_leftover_perf_join.py +137 -0
- simdref-0.0.15/tests/test_leftover_riscv_shared.py +109 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_lsp_stdio.py +107 -22
- simdref-0.0.15/tests/test_no_catalog_row.py +208 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_perf.py +31 -0
- simdref-0.0.15/tests/test_source_validation.py +417 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_storage_payload.py +36 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_storage_schema.py +3 -3
- simdref-0.0.15/tests/test_vendor_riscv_categories.py +80 -0
- simdref-0.0.15/tests/test_vendor_riscv_sources.py +35 -0
- simdref-0.0.13/src/simdref/lsp.py +0 -811
- simdref-0.0.13/src/simdref.egg-info/requires.txt +0 -10
- simdref-0.0.13/tests/test_source_validation.py +0 -48
- {simdref-0.0.13 → simdref-0.0.15}/LICENSE +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/setup.cfg +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/__init__.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/__main__.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/filters.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/ingest.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/ingest_pdf.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/ingest_sources.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/manpages.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/pdfparse/__init__.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/pdfparse/base.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/pdfparse/intel.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/pdfparse/registry.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/pdfparse/types.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/pdfrefs.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/perf_sources/__init__.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/perf_sources/cores.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/perf_sources/llvm_mca.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/perf_sources/merge.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/queries.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/search.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref/ui_labels.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref.egg-info/dependency_links.txt +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref.egg-info/entry_points.txt +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/src/simdref.egg-info/top_level.txt +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_annotate.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_annotate_positions.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_audit_coverage.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_cli_bare_query.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_cli_bootstrap_progress.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_cli_help.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_cli_llm.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_coverage_live.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_coverage_parity.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_filters.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_ingest_sources.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_intel_operation_and_url.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_issue2_fixes.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_issues_23_24_25.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_lsp_web.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_models.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_pdfparse.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_perf_sources.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_presentation.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_preset_persistence.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_riscv_docs_pages.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_search.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_search_pushdown.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_source_kind_filter.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_tui.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_ui_labels_parity.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_version_matches_pyproject.py +0 -0
- {simdref-0.0.13 → simdref-0.0.15}/tests/test_x86_linking.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simdref
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.15
|
|
4
4
|
Summary: Searchable SIMD intrinsic and instruction reference with CLI, manpages, TUI, and LSP.
|
|
5
5
|
Author: Marco
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -28,6 +28,7 @@ Requires-Dist: PyMuPDF<2,>=1.28
|
|
|
28
28
|
Requires-Dist: PyYAML<7,>=6.0.3
|
|
29
29
|
Requires-Dist: rapidfuzz<4,>=3.14
|
|
30
30
|
Requires-Dist: rich<16,>=15
|
|
31
|
+
Requires-Dist: simdref-llvm-mc==23.1.4; (sys_platform == "linux" and (platform_machine == "x86_64" or platform_machine == "aarch64")) or (sys_platform == "darwin" and (platform_machine == "arm64" or platform_machine == "x86_64")) or (sys_platform == "win32" and platform_machine == "AMD64")
|
|
31
32
|
Requires-Dist: textual<9,>=8.2
|
|
32
33
|
Requires-Dist: typer<1,>=0.27
|
|
33
34
|
Dynamic: license-file
|
|
@@ -106,7 +107,7 @@ simdref-lsp
|
|
|
106
107
|
|
|
107
108
|
Editor clients, one for each editor:
|
|
108
109
|
|
|
109
|
-
- Zed: [zed-
|
|
110
|
+
- Zed: [zed-asm-intrinsics](https://github.com/simd-labs/zed-asm-intrinsics)
|
|
110
111
|
- VS Code: [vscode-simdref](https://github.com/simd-labs/vscode-simdref)
|
|
111
112
|
- JetBrains: [jetbrains-simdref](https://github.com/simd-labs/jetbrains-simdref)
|
|
112
113
|
- Neovim: [nvim-simdref](https://github.com/simd-labs/nvim-simdref)
|
|
@@ -72,7 +72,7 @@ simdref-lsp
|
|
|
72
72
|
|
|
73
73
|
Editor clients, one for each editor:
|
|
74
74
|
|
|
75
|
-
- Zed: [zed-
|
|
75
|
+
- Zed: [zed-asm-intrinsics](https://github.com/simd-labs/zed-asm-intrinsics)
|
|
76
76
|
- VS Code: [vscode-simdref](https://github.com/simd-labs/vscode-simdref)
|
|
77
77
|
- JetBrains: [jetbrains-simdref](https://github.com/simd-labs/jetbrains-simdref)
|
|
78
78
|
- Neovim: [nvim-simdref](https://github.com/simd-labs/nvim-simdref)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "simdref"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.15"
|
|
8
8
|
description = "Searchable SIMD intrinsic and instruction reference with CLI, manpages, TUI, and LSP."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -41,6 +41,11 @@ dependencies = [
|
|
|
41
41
|
"PyYAML>=6.0.3,<7",
|
|
42
42
|
"rapidfuzz>=3.14,<4",
|
|
43
43
|
"rich>=15,<16",
|
|
44
|
+
# Pinned to the LLVM the catalog build used. Wheels exist only for
|
|
45
|
+
# manylinux x86_64 aarch64, macOS arm64 x86_64 and win_amd64, so the
|
|
46
|
+
# marker keeps the dependency off every other platform (musl included),
|
|
47
|
+
# where pip must see no candidate instead of failing to resolve.
|
|
48
|
+
'simdref-llvm-mc==23.1.4; (sys_platform == "linux" and (platform_machine == "x86_64" or platform_machine == "aarch64")) or (sys_platform == "darwin" and (platform_machine == "arm64" or platform_machine == "x86_64")) or (sys_platform == "win32" and platform_machine == "AMD64")',
|
|
44
49
|
"textual>=8.2,<9",
|
|
45
50
|
"typer>=0.27,<1",
|
|
46
51
|
]
|
|
@@ -17,8 +17,9 @@ from enum import Enum
|
|
|
17
17
|
from typing import Any, Iterable, Iterator
|
|
18
18
|
|
|
19
19
|
from simdref import perf
|
|
20
|
+
from simdref.lsp import NO_CATALOG_ROW, identity_keys
|
|
20
21
|
from simdref.models import InstructionRecord
|
|
21
|
-
from simdref.storage import load_instructions_by_mnemonic_from_db
|
|
22
|
+
from simdref.storage import load_instruction_from_db, load_instructions_by_mnemonic_from_db
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class LineKind(str, Enum):
|
|
@@ -410,6 +411,9 @@ def _operand_match_score(
|
|
|
410
411
|
return score
|
|
411
412
|
|
|
412
413
|
|
|
414
|
+
# shortcut: the form pick answers only when byte identity has no row
|
|
415
|
+
# (schema-13 catalogs or missing tools); remove it at the schema-14 data
|
|
416
|
+
# release with simdref-llvm-mc on PyPI as a required dependency
|
|
413
417
|
def pick_record(
|
|
414
418
|
records: list[InstructionRecord],
|
|
415
419
|
*,
|
|
@@ -456,7 +460,9 @@ class PerfSummary:
|
|
|
456
460
|
def _per_arch_value(details: dict[str, Any], value_fn) -> float | None:
|
|
457
461
|
for v in value_fn(details):
|
|
458
462
|
try:
|
|
459
|
-
|
|
463
|
+
# Latency ranges render as "min-max" strings (simdref#50); the
|
|
464
|
+
# float view takes the min so aggregates stay numeric.
|
|
465
|
+
return float(str(v).split("-", 1)[0])
|
|
460
466
|
except (TypeError, ValueError):
|
|
461
467
|
continue
|
|
462
468
|
return None
|
|
@@ -679,22 +685,48 @@ def _annotate_instruction(
|
|
|
679
685
|
conn: sqlite3.Connection,
|
|
680
686
|
lookup_cache: dict[str, list[InstructionRecord]] | None = None,
|
|
681
687
|
stats: dict[str, int] | None = None,
|
|
688
|
+
identity_key: str | None = None,
|
|
682
689
|
) -> tuple[str, dict[str, Any] | None]:
|
|
683
690
|
"""Return the rendered output line and an optional JSON record."""
|
|
684
|
-
if lookup_cache is not None:
|
|
685
|
-
records = lookup_cache.get(parsed.mnemonic)
|
|
686
|
-
if records is None:
|
|
687
|
-
# Module-level call (not a captured reference) so tests can
|
|
688
|
-
# keep monkeypatching ``annotate.lookup`` with a 2-arg fake.
|
|
689
|
-
records = lookup(parsed.mnemonic, conn)
|
|
690
|
-
lookup_cache[parsed.mnemonic] = records
|
|
691
|
-
else:
|
|
692
|
-
records = lookup(parsed.mnemonic, conn)
|
|
693
691
|
if stats is not None:
|
|
694
692
|
stats["parsed"] = stats.get("parsed", 0) + 1
|
|
695
|
-
|
|
693
|
+
if identity_key == NO_CATALOG_ROW:
|
|
694
|
+
# Byte identity identified the form and the catalog has no row for
|
|
695
|
+
# it (issue #49: uops.info carries no AVX reg-reg VMOVAPD/VMOVUPS):
|
|
696
|
+
# report the miss by name, never guess a neighbour form below.
|
|
697
|
+
if stats is not None:
|
|
698
|
+
stats["no_row"] = stats.get("no_row", 0) + 1
|
|
699
|
+
no_row: dict[str, Any] = {
|
|
700
|
+
"mnemonic": parsed.mnemonic,
|
|
701
|
+
"known": False,
|
|
702
|
+
"no_measured_row": True,
|
|
703
|
+
}
|
|
704
|
+
if parsed.address is not None:
|
|
705
|
+
no_row["address"] = f"0x{parsed.address:x}"
|
|
706
|
+
if parsed.source_file:
|
|
707
|
+
no_row["source_file"] = parsed.source_file
|
|
708
|
+
if parsed.source_line is not None:
|
|
709
|
+
no_row["source_line"] = parsed.source_line
|
|
710
|
+
return f"{parsed.raw} # {parsed.mnemonic}: no measured row", no_row
|
|
711
|
+
# Byte identity wins over the form pick: the record is what the line
|
|
712
|
+
# assembles to, and an empty row keeps the old pick unchanged.
|
|
713
|
+
record = load_instruction_from_db(conn, identity_key) if identity_key else None
|
|
714
|
+
if record is not None:
|
|
715
|
+
if stats is not None:
|
|
716
|
+
stats["recognized"] = stats.get("recognized", 0) + 1
|
|
717
|
+
else:
|
|
718
|
+
if lookup_cache is not None:
|
|
719
|
+
records = lookup_cache.get(parsed.mnemonic)
|
|
720
|
+
if records is None:
|
|
721
|
+
# Module-level call (not a captured reference) so tests can
|
|
722
|
+
# keep monkeypatching ``annotate.lookup`` with a 2-arg fake.
|
|
723
|
+
records = lookup(parsed.mnemonic, conn)
|
|
724
|
+
lookup_cache[parsed.mnemonic] = records
|
|
725
|
+
else:
|
|
726
|
+
records = lookup(parsed.mnemonic, conn)
|
|
727
|
+
if stats is not None and records:
|
|
696
728
|
stats["recognized"] = stats.get("recognized", 0) + 1
|
|
697
|
-
|
|
729
|
+
record = pick_record(records, arch=opts.arch, operands=parsed.operands)
|
|
698
730
|
|
|
699
731
|
if record is None:
|
|
700
732
|
if opts.unknown == "drop":
|
|
@@ -784,8 +816,29 @@ def annotate_stream(
|
|
|
784
816
|
pending_src_line: int | None = None
|
|
785
817
|
lookup_cache: dict[str, list[InstructionRecord]] = {}
|
|
786
818
|
|
|
787
|
-
for line in lines
|
|
788
|
-
|
|
819
|
+
parsed_lines = [parse_asm_line(line, track_positions=opts.track_positions) for line in lines]
|
|
820
|
+
# One llvm-mc run for the whole input: byte identity per instruction
|
|
821
|
+
# line. An objdump line assembles as its mnemonic and operands only, so
|
|
822
|
+
# the address and byte columns never reach the assembler.
|
|
823
|
+
items = [
|
|
824
|
+
(
|
|
825
|
+
index,
|
|
826
|
+
f"{parsed.mnemonic} {parsed.operands}".strip()
|
|
827
|
+
if parsed.address is not None
|
|
828
|
+
else parsed.raw,
|
|
829
|
+
)
|
|
830
|
+
for index, parsed in enumerate(parsed_lines)
|
|
831
|
+
if parsed.kind == LineKind.INSTRUCTION
|
|
832
|
+
]
|
|
833
|
+
identity = {
|
|
834
|
+
index: key
|
|
835
|
+
for (index, _text), key in zip(
|
|
836
|
+
items, identity_keys([text for _index, text in items], None, conn)
|
|
837
|
+
)
|
|
838
|
+
if key
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
for index, parsed in enumerate(parsed_lines):
|
|
789
842
|
if opts.track_positions:
|
|
790
843
|
if parsed.kind == LineKind.INSTRUCTION:
|
|
791
844
|
parsed.source_file = pending_src_file
|
|
@@ -815,7 +868,9 @@ def annotate_stream(
|
|
|
815
868
|
if not collecting_json:
|
|
816
869
|
yield parsed.raw + "\n"
|
|
817
870
|
continue
|
|
818
|
-
out_line, record = _annotate_instruction(
|
|
871
|
+
out_line, record = _annotate_instruction(
|
|
872
|
+
parsed, opts, conn, lookup_cache, stats, identity_key=identity.get(index)
|
|
873
|
+
)
|
|
819
874
|
if collecting_json:
|
|
820
875
|
if record is not None:
|
|
821
876
|
json_records.append(record)
|
|
@@ -256,6 +256,18 @@ def _candidate_instruction_lists(payload: Any) -> list[list[dict[str, Any]]]:
|
|
|
256
256
|
return lists
|
|
257
257
|
|
|
258
258
|
|
|
259
|
+
def _is_aarchmrs_tree(payload: Any) -> bool:
|
|
260
|
+
top = payload.get("instructions") if isinstance(payload, dict) else None
|
|
261
|
+
return (
|
|
262
|
+
isinstance(top, list)
|
|
263
|
+
and bool(top)
|
|
264
|
+
and all(
|
|
265
|
+
isinstance(node, dict) and str(node.get("_type", "")).endswith("InstructionSet")
|
|
266
|
+
for node in top
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
|
|
259
271
|
def _records_from_payload(payload: Any) -> list[InstructionRecord]:
|
|
260
272
|
if isinstance(payload, dict) and payload.get("format") == "arm-aarchmrs-instructions-v1":
|
|
261
273
|
instructions_json = str(payload.get("instructions_json") or "[]")
|
|
@@ -264,9 +276,8 @@ def _records_from_payload(payload: Any) -> list[InstructionRecord]:
|
|
|
264
276
|
if isinstance(payload, dict) and payload.get("format") == "arm-instructions-fixture-v1":
|
|
265
277
|
return _records_from_payload(payload.get("instructions") or [])
|
|
266
278
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
return aarchmrs
|
|
279
|
+
if _is_aarchmrs_tree(payload):
|
|
280
|
+
return _records_from_aarchmrs_tree(payload)
|
|
270
281
|
|
|
271
282
|
for candidates in _candidate_instruction_lists(payload):
|
|
272
283
|
records = [_normalize_instruction_item(item) for item in candidates]
|
|
@@ -280,6 +291,44 @@ def _records_from_payload(payload: Any) -> list[InstructionRecord]:
|
|
|
280
291
|
# AARCHMRS (official Arm machine-readable spec) tree walker
|
|
281
292
|
# ---------------------------------------------------------------------------
|
|
282
293
|
|
|
294
|
+
|
|
295
|
+
def _encodeset_mask_match(encoding: Any) -> tuple[int, int, int] | None:
|
|
296
|
+
"""Return (bits, mask, match) for an AARCHMRS encodeset, or None.
|
|
297
|
+
|
|
298
|
+
Every encodeset entry value with a fixed '0'/'1' pattern adds mask and
|
|
299
|
+
match bits; 'x' entries (operand fields) add nothing. An entry with a
|
|
300
|
+
non-binary value or a length that disagrees with its range makes the
|
|
301
|
+
whole encodeset unusable as a key.
|
|
302
|
+
"""
|
|
303
|
+
if not isinstance(encoding, dict):
|
|
304
|
+
return None
|
|
305
|
+
width = encoding.get("width")
|
|
306
|
+
values = encoding.get("values")
|
|
307
|
+
if width != 32 or not isinstance(values, list):
|
|
308
|
+
return None
|
|
309
|
+
mask = match = 0
|
|
310
|
+
for entry in values:
|
|
311
|
+
if not isinstance(entry, dict):
|
|
312
|
+
return None
|
|
313
|
+
value = str(((entry.get("value") or {}).get("value")) or "").strip("'")
|
|
314
|
+
rng = entry.get("range") or {}
|
|
315
|
+
start, field_width = rng.get("start"), rng.get("width")
|
|
316
|
+
if not isinstance(start, int) or not isinstance(field_width, int):
|
|
317
|
+
return None
|
|
318
|
+
if len(value) != field_width:
|
|
319
|
+
return None
|
|
320
|
+
for i, char in enumerate(value):
|
|
321
|
+
bit = start + field_width - 1 - i
|
|
322
|
+
if char == "x":
|
|
323
|
+
continue
|
|
324
|
+
if char not in "01":
|
|
325
|
+
return None
|
|
326
|
+
mask |= 1 << bit
|
|
327
|
+
if char == "1":
|
|
328
|
+
match |= 1 << bit
|
|
329
|
+
return 32, mask, match
|
|
330
|
+
|
|
331
|
+
|
|
283
332
|
_RULE_PLACEHOLDER: dict[str, str] = {
|
|
284
333
|
# Scalar
|
|
285
334
|
"Xd": "x0",
|
|
@@ -311,6 +360,13 @@ _RULE_PLACEHOLDER: dict[str, str] = {
|
|
|
311
360
|
"Dn": "d1",
|
|
312
361
|
"Dm": "d2",
|
|
313
362
|
"Da": "d3",
|
|
363
|
+
# FP accumulator register operands; t/s letters follow the Xt/Xs numbering.
|
|
364
|
+
"fpfar_dt": "d0",
|
|
365
|
+
"fpfar_ds": "d1",
|
|
366
|
+
"fpfar_st": "s0",
|
|
367
|
+
"fpfar_ss": "s1",
|
|
368
|
+
"fpfar_ht": "h0",
|
|
369
|
+
"fpfar_hs": "h1",
|
|
314
370
|
"Sd": "s0",
|
|
315
371
|
"Sn": "s1",
|
|
316
372
|
"Sm": "s2",
|
|
@@ -451,11 +507,16 @@ def _records_from_aarchmrs_tree(payload: Any) -> list[InstructionRecord]:
|
|
|
451
507
|
return []
|
|
452
508
|
|
|
453
509
|
records: list[InstructionRecord] = []
|
|
454
|
-
seen_keys:
|
|
510
|
+
seen_keys: dict[tuple[str, str], tuple[int, int]] = {}
|
|
455
511
|
|
|
456
512
|
def walk(node: dict[str, Any], group_path: list[str]) -> None:
|
|
457
513
|
node_type = str(node.get("_type", ""))
|
|
458
514
|
if node_type.endswith("Instruction"):
|
|
515
|
+
enc = _encodeset_mask_match(node.get("encoding"))
|
|
516
|
+
if enc is None:
|
|
517
|
+
enc_bits = enc_mask = enc_match = 0
|
|
518
|
+
else:
|
|
519
|
+
enc_bits, enc_mask, enc_match = enc
|
|
459
520
|
assembly = node.get("assembly")
|
|
460
521
|
if not isinstance(assembly, dict):
|
|
461
522
|
return
|
|
@@ -463,15 +524,25 @@ def _records_from_aarchmrs_tree(payload: Any) -> list[InstructionRecord]:
|
|
|
463
524
|
if not rendered:
|
|
464
525
|
return
|
|
465
526
|
head, _, tail = rendered.partition(" ")
|
|
466
|
-
|
|
467
|
-
|
|
527
|
+
# Head tokens can glue a literal or rule placeholder onto the
|
|
528
|
+
# mnemonic (RET<Rn_option>, DCPS1#0, B.<cond_option>): take the
|
|
529
|
+
# leading alphanumeric run instead of dropping the whole node.
|
|
530
|
+
mnemonic_match = re.match(r"[A-Z][A-Z0-9]*", head.strip().upper())
|
|
531
|
+
if not mnemonic_match:
|
|
468
532
|
return
|
|
533
|
+
mnemonic = mnemonic_match.group(0)
|
|
469
534
|
operand_form = tail.strip()
|
|
470
535
|
form = _canonical_instruction_key(mnemonic, operand_form) if operand_form else mnemonic
|
|
471
536
|
key = (mnemonic, form.casefold())
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
537
|
+
seen_enc = seen_keys.get(key)
|
|
538
|
+
if seen_enc is not None:
|
|
539
|
+
if seen_enc == (enc_mask, enc_match):
|
|
540
|
+
return
|
|
541
|
+
# Sibling encodings of one rendered form stay separate rows;
|
|
542
|
+
# their keys are never merged (spec K4).
|
|
543
|
+
form = f"{form} [{str(node.get('name') or '').strip()}]"
|
|
544
|
+
key = (mnemonic, form.casefold())
|
|
545
|
+
seen_keys[key] = (enc_mask, enc_match)
|
|
475
546
|
summary = _generated_summary(mnemonic)
|
|
476
547
|
if not summary.endswith("."):
|
|
477
548
|
summary += "."
|
|
@@ -490,6 +561,9 @@ def _records_from_aarchmrs_tree(payload: Any) -> list[InstructionRecord]:
|
|
|
490
561
|
isa=_infer_aarchmrs_isa(group_path),
|
|
491
562
|
metadata=metadata,
|
|
492
563
|
source="arm-a64",
|
|
564
|
+
enc_bits=enc_bits,
|
|
565
|
+
enc_mask=enc_mask,
|
|
566
|
+
enc_match=enc_match,
|
|
493
567
|
)
|
|
494
568
|
)
|
|
495
569
|
return
|
|
@@ -503,8 +577,15 @@ def _records_from_aarchmrs_tree(payload: Any) -> list[InstructionRecord]:
|
|
|
503
577
|
walk(child, next_path)
|
|
504
578
|
|
|
505
579
|
for top_node in top:
|
|
506
|
-
if isinstance(top_node, dict):
|
|
507
|
-
|
|
580
|
+
if not isinstance(top_node, dict):
|
|
581
|
+
continue
|
|
582
|
+
set_name = str(top_node.get("name") or "")
|
|
583
|
+
# A32/T32 encodings cannot be scoped from a bare word (their patterns
|
|
584
|
+
# overlap A32 condition space), and the byte key is defined on the A64
|
|
585
|
+
# instruction set only (spec K2/K4).
|
|
586
|
+
if set_name != "A64":
|
|
587
|
+
continue
|
|
588
|
+
walk(top_node, [set_name])
|
|
508
589
|
return records
|
|
509
590
|
|
|
510
591
|
|