simdref 0.0.16__tar.gz → 0.0.17__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.
Files changed (169) hide show
  1. {simdref-0.0.16/src/simdref.egg-info → simdref-0.0.17}/PKG-INFO +3 -2
  2. {simdref-0.0.16 → simdref-0.0.17}/README.md +1 -0
  3. {simdref-0.0.16 → simdref-0.0.17}/pyproject.toml +2 -2
  4. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/annotate.py +144 -27
  5. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/arm_instructions.py +0 -1
  6. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/asm_identity.py +131 -86
  7. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/asmline.py +106 -7
  8. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/catkey.py +10 -1
  9. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/cli.py +119 -67
  10. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/display.py +2 -0
  11. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/filters.py +1 -0
  12. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/ingest.py +2 -0
  13. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/ingest_catalog.py +7 -1
  14. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/ingest_sources.py +28 -0
  15. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/lsp.py +191 -25
  16. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/manpages.py +20 -8
  17. simdref-0.0.17/src/simdref/ptx.py +759 -0
  18. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/riscv.py +0 -1
  19. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/storage.py +10 -0
  20. {simdref-0.0.16 → simdref-0.0.17/src/simdref.egg-info}/PKG-INFO +3 -2
  21. {simdref-0.0.16 → simdref-0.0.17}/src/simdref.egg-info/SOURCES.txt +5 -0
  22. {simdref-0.0.16 → simdref-0.0.17}/src/simdref.egg-info/requires.txt +1 -1
  23. simdref-0.0.17/tests/test_annotate_scans.py +52 -0
  24. {simdref-0.0.16 → simdref-0.0.17}/tests/test_auto_update_on_version_change.py +1 -0
  25. {simdref-0.0.16 → simdref-0.0.17}/tests/test_catkey.py +2 -0
  26. {simdref-0.0.16 → simdref-0.0.17}/tests/test_cli_bare_query.py +34 -0
  27. simdref-0.0.17/tests/test_fetch_retry.py +175 -0
  28. {simdref-0.0.16 → simdref-0.0.17}/tests/test_ingest_sources.py +20 -0
  29. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_65_identity.py +40 -15
  30. simdref-0.0.17/tests/test_lsp_ptx.py +311 -0
  31. {simdref-0.0.16 → simdref-0.0.17}/tests/test_lsp_stdio.py +202 -1
  32. {simdref-0.0.16 → simdref-0.0.17}/tests/test_presentation.py +25 -0
  33. simdref-0.0.17/tests/test_ptx.py +253 -0
  34. {simdref-0.0.16 → simdref-0.0.17}/LICENSE +0 -0
  35. {simdref-0.0.16 → simdref-0.0.17}/setup.cfg +0 -0
  36. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/__init__.py +0 -0
  37. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/__main__.py +0 -0
  38. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/export.py +0 -0
  39. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/htmltext.py +0 -0
  40. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/ingest_pdf.py +0 -0
  41. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/models.py +0 -0
  42. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/operands.py +0 -0
  43. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/pdfparse/__init__.py +0 -0
  44. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/pdfparse/base.py +0 -0
  45. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/pdfparse/intel.py +0 -0
  46. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/pdfparse/registry.py +0 -0
  47. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/pdfparse/types.py +0 -0
  48. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/pdfrefs.py +0 -0
  49. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/perf.py +0 -0
  50. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/perf_sources/__init__.py +0 -0
  51. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/perf_sources/amd_swopt.py +0 -0
  52. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/perf_sources/arm_swopt.py +0 -0
  53. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/perf_sources/cores.py +0 -0
  54. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/perf_sources/instlatx64.py +0 -0
  55. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/perf_sources/intel_guide.py +0 -0
  56. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/perf_sources/intel_optref.py +0 -0
  57. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/perf_sources/llvm_mca.py +0 -0
  58. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
  59. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/perf_sources/merge.py +0 -0
  60. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/queries.py +0 -0
  61. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/regtable.py +0 -0
  62. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/search.py +0 -0
  63. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/tablecols.py +0 -0
  64. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/tui.py +0 -0
  65. {simdref-0.0.16 → simdref-0.0.17}/src/simdref/ui_labels.py +0 -0
  66. {simdref-0.0.16 → simdref-0.0.17}/src/simdref.egg-info/dependency_links.txt +0 -0
  67. {simdref-0.0.16 → simdref-0.0.17}/src/simdref.egg-info/entry_points.txt +0 -0
  68. {simdref-0.0.16 → simdref-0.0.17}/src/simdref.egg-info/top_level.txt +0 -0
  69. {simdref-0.0.16 → simdref-0.0.17}/tests/test_a1_sdm_parser.py +0 -0
  70. {simdref-0.0.16 → simdref-0.0.17}/tests/test_a2_perf_rule.py +0 -0
  71. {simdref-0.0.16 → simdref-0.0.17}/tests/test_a4_arm_pdfrefs.py +0 -0
  72. {simdref-0.0.16 → simdref-0.0.17}/tests/test_a5_offline_aarchmrs.py +0 -0
  73. {simdref-0.0.16 → simdref-0.0.17}/tests/test_amd_match_llvm_mc.py +0 -0
  74. {simdref-0.0.16 → simdref-0.0.17}/tests/test_annotate.py +0 -0
  75. {simdref-0.0.16 → simdref-0.0.17}/tests/test_annotate_positions.py +0 -0
  76. {simdref-0.0.16 → simdref-0.0.17}/tests/test_arrangement_tail.py +0 -0
  77. {simdref-0.0.16 → simdref-0.0.17}/tests/test_asm_identity.py +0 -0
  78. {simdref-0.0.16 → simdref-0.0.17}/tests/test_asm_wire.py +0 -0
  79. {simdref-0.0.16 → simdref-0.0.17}/tests/test_asmline.py +0 -0
  80. {simdref-0.0.16 → simdref-0.0.17}/tests/test_audit_coverage.py +0 -0
  81. {simdref-0.0.16 → simdref-0.0.17}/tests/test_b1_latency_pairs.py +0 -0
  82. {simdref-0.0.16 → simdref-0.0.17}/tests/test_b2_metadata.py +0 -0
  83. {simdref-0.0.16 → simdref-0.0.17}/tests/test_b3_sdm_forms.py +0 -0
  84. {simdref-0.0.16 → simdref-0.0.17}/tests/test_b4_descriptions_provenance.py +0 -0
  85. {simdref-0.0.16 → simdref-0.0.17}/tests/test_b5_linking.py +0 -0
  86. {simdref-0.0.16 → simdref-0.0.17}/tests/test_b5_perf_family.py +0 -0
  87. {simdref-0.0.16 → simdref-0.0.17}/tests/test_b5_sme.py +0 -0
  88. {simdref-0.0.16 → simdref-0.0.17}/tests/test_c0_perf_priority.py +0 -0
  89. {simdref-0.0.16 → simdref-0.0.17}/tests/test_cli_bootstrap_progress.py +0 -0
  90. {simdref-0.0.16 → simdref-0.0.17}/tests/test_cli_help.py +0 -0
  91. {simdref-0.0.16 → simdref-0.0.17}/tests/test_cli_llm.py +0 -0
  92. {simdref-0.0.16 → simdref-0.0.17}/tests/test_coverage_live.py +0 -0
  93. {simdref-0.0.16 → simdref-0.0.17}/tests/test_coverage_parity.py +0 -0
  94. {simdref-0.0.16 → simdref-0.0.17}/tests/test_display.py +0 -0
  95. {simdref-0.0.16 → simdref-0.0.17}/tests/test_display_scanners.py +0 -0
  96. {simdref-0.0.16 → simdref-0.0.17}/tests/test_export.py +0 -0
  97. {simdref-0.0.16 → simdref-0.0.17}/tests/test_export_viz.py +0 -0
  98. {simdref-0.0.16 → simdref-0.0.17}/tests/test_filters.py +0 -0
  99. {simdref-0.0.16 → simdref-0.0.17}/tests/test_fixui_display.py +0 -0
  100. {simdref-0.0.16 → simdref-0.0.17}/tests/test_htmltext.py +0 -0
  101. {simdref-0.0.16 → simdref-0.0.17}/tests/test_ingest_enc_fields.py +0 -0
  102. {simdref-0.0.16 → simdref-0.0.17}/tests/test_int_arm_isa_label.py +0 -0
  103. {simdref-0.0.16 → simdref-0.0.17}/tests/test_int_core_ids.py +0 -0
  104. {simdref-0.0.16 → simdref-0.0.17}/tests/test_int_ingest_registration.py +0 -0
  105. {simdref-0.0.16 → simdref-0.0.17}/tests/test_int_sdm_family_fallback.py +0 -0
  106. {simdref-0.0.16 → simdref-0.0.17}/tests/test_intel_operation_and_url.py +0 -0
  107. {simdref-0.0.16 → simdref-0.0.17}/tests/test_isa_detect_llvm_mc.py +0 -0
  108. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue2_fixes.py +0 -0
  109. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_51.py +0 -0
  110. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_52.py +0 -0
  111. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_53_54.py +0 -0
  112. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_55.py +0 -0
  113. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_56_57a.py +0 -0
  114. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_57_cpi.py +0 -0
  115. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_60_c1.py +0 -0
  116. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_60_contract.py +0 -0
  117. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_60_crossarch.py +0 -0
  118. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_60_matcher.py +0 -0
  119. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_65_arm_alias.py +0 -0
  120. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_65_compressed.py +0 -0
  121. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_65_m1.py +0 -0
  122. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_65_perfdata.py +0 -0
  123. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_65_text.py +0 -0
  124. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issue_65_timing.py +0 -0
  125. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issues_23_24_25.py +0 -0
  126. {simdref-0.0.16 → simdref-0.0.17}/tests/test_issues_51_57_join.py +0 -0
  127. {simdref-0.0.16 → simdref-0.0.17}/tests/test_leftover_arm_form_pick.py +0 -0
  128. {simdref-0.0.16 → simdref-0.0.17}/tests/test_leftover_arm_postindex.py +0 -0
  129. {simdref-0.0.16 → simdref-0.0.17}/tests/test_leftover_att_movx.py +0 -0
  130. {simdref-0.0.16 → simdref-0.0.17}/tests/test_leftover_autoupdate.py +0 -0
  131. {simdref-0.0.16 → simdref-0.0.17}/tests/test_leftover_catalog_gaps.py +0 -0
  132. {simdref-0.0.16 → simdref-0.0.17}/tests/test_leftover_ingest_isa_merge.py +0 -0
  133. {simdref-0.0.16 → simdref-0.0.17}/tests/test_leftover_intel_size.py +0 -0
  134. {simdref-0.0.16 → simdref-0.0.17}/tests/test_leftover_perf_join.py +0 -0
  135. {simdref-0.0.16 → simdref-0.0.17}/tests/test_leftover_riscv_shared.py +0 -0
  136. {simdref-0.0.16 → simdref-0.0.17}/tests/test_lsp_web.py +0 -0
  137. {simdref-0.0.16 → simdref-0.0.17}/tests/test_models.py +0 -0
  138. {simdref-0.0.16 → simdref-0.0.17}/tests/test_no_catalog_row.py +0 -0
  139. {simdref-0.0.16 → simdref-0.0.17}/tests/test_objdump_listings.py +0 -0
  140. {simdref-0.0.16 → simdref-0.0.17}/tests/test_operands.py +0 -0
  141. {simdref-0.0.16 → simdref-0.0.17}/tests/test_pdfparse.py +0 -0
  142. {simdref-0.0.16 → simdref-0.0.17}/tests/test_perf.py +0 -0
  143. {simdref-0.0.16 → simdref-0.0.17}/tests/test_perf_source_amd_swopt.py +0 -0
  144. {simdref-0.0.16 → simdref-0.0.17}/tests/test_perf_source_arm_swopt.py +0 -0
  145. {simdref-0.0.16 → simdref-0.0.17}/tests/test_perf_source_instlatx64.py +0 -0
  146. {simdref-0.0.16 → simdref-0.0.17}/tests/test_perf_source_intel_guide.py +0 -0
  147. {simdref-0.0.16 → simdref-0.0.17}/tests/test_perf_source_intel_optref.py +0 -0
  148. {simdref-0.0.16 → simdref-0.0.17}/tests/test_perf_sources.py +0 -0
  149. {simdref-0.0.16 → simdref-0.0.17}/tests/test_preset_persistence.py +0 -0
  150. {simdref-0.0.16 → simdref-0.0.17}/tests/test_regtable.py +0 -0
  151. {simdref-0.0.16 → simdref-0.0.17}/tests/test_release_tag_pin.py +0 -0
  152. {simdref-0.0.16 → simdref-0.0.17}/tests/test_riscv_docs_pages.py +0 -0
  153. {simdref-0.0.16 → simdref-0.0.17}/tests/test_search.py +0 -0
  154. {simdref-0.0.16 → simdref-0.0.17}/tests/test_search_pushdown.py +0 -0
  155. {simdref-0.0.16 → simdref-0.0.17}/tests/test_shared_source_helpers.py +0 -0
  156. {simdref-0.0.16 → simdref-0.0.17}/tests/test_source_kind_filter.py +0 -0
  157. {simdref-0.0.16 → simdref-0.0.17}/tests/test_source_validation.py +0 -0
  158. {simdref-0.0.16 → simdref-0.0.17}/tests/test_storage_alnum.py +0 -0
  159. {simdref-0.0.16 → simdref-0.0.17}/tests/test_storage_payload.py +0 -0
  160. {simdref-0.0.16 → simdref-0.0.17}/tests/test_storage_schema.py +0 -0
  161. {simdref-0.0.16 → simdref-0.0.17}/tests/test_tablecols.py +0 -0
  162. {simdref-0.0.16 → simdref-0.0.17}/tests/test_tui.py +0 -0
  163. {simdref-0.0.16 → simdref-0.0.17}/tests/test_ui_labels_parity.py +0 -0
  164. {simdref-0.0.16 → simdref-0.0.17}/tests/test_update_build_stamp.py +0 -0
  165. {simdref-0.0.16 → simdref-0.0.17}/tests/test_vendor_riscv_categories.py +0 -0
  166. {simdref-0.0.16 → simdref-0.0.17}/tests/test_vendor_riscv_sources.py +0 -0
  167. {simdref-0.0.16 → simdref-0.0.17}/tests/test_version_matches_pyproject.py +0 -0
  168. {simdref-0.0.16 → simdref-0.0.17}/tests/test_x86_key_widths.py +0 -0
  169. {simdref-0.0.16 → simdref-0.0.17}/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.16
3
+ Version: 0.0.17
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
@@ -25,7 +25,7 @@ Requires-Dist: msgpack<2,>=1.2.4
25
25
  Requires-Dist: PyYAML<7,>=6.0.3
26
26
  Requires-Dist: rapidfuzz<4,>=3.14.6
27
27
  Requires-Dist: rich<16,>=15.0.0
28
- 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")
28
+ Requires-Dist: simdref-llvm-mc==23.1.4.post1; (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")
29
29
  Requires-Dist: textual<9,>=8.2.8
30
30
  Requires-Dist: typer<1,>=0.27.3
31
31
  Provides-Extra: ingest
@@ -177,6 +177,7 @@ Dev commands
177
177
  | Arm AARCHMRS (A64) | Base instruction forms and operand tables | live-only² |
178
178
  | riscv-rvv-intrinsic-doc | RVV intrinsics with deterministic instruction refs | 77,285 intrinsics |
179
179
  | RISC-V unified-db | RVV instruction forms, ISA tags, description, operation | 3,666 instructions |
180
+ | NVIDIA PTX ISA | Instruction ops, syntax, qualifiers, versions, sm_XX | 1,088 instructions |
180
181
  | Intel Guide perf tables | Per-core latency and throughput | 9,134 rows |
181
182
  | Intel Optimization Ref. | Latency and throughput tables (volume 2) | 3,155 rows |
182
183
  | InstLatX64 | Measured latency and throughput dumps | 16,235 rows |
@@ -142,6 +142,7 @@ Dev commands
142
142
  | Arm AARCHMRS (A64) | Base instruction forms and operand tables | live-only² |
143
143
  | riscv-rvv-intrinsic-doc | RVV intrinsics with deterministic instruction refs | 77,285 intrinsics |
144
144
  | RISC-V unified-db | RVV instruction forms, ISA tags, description, operation | 3,666 instructions |
145
+ | NVIDIA PTX ISA | Instruction ops, syntax, qualifiers, versions, sm_XX | 1,088 instructions |
145
146
  | Intel Guide perf tables | Per-core latency and throughput | 9,134 rows |
146
147
  | Intel Optimization Ref. | Latency and throughput tables (volume 2) | 3,155 rows |
147
148
  | InstLatX64 | Measured latency and throughput dumps | 16,235 rows |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simdref"
7
- version = "0.0.16"
7
+ version = "0.0.17"
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.11"
@@ -42,7 +42,7 @@ dependencies = [
42
42
  # manylinux x86_64 aarch64, macOS arm64 x86_64 and win_amd64, so the
43
43
  # marker keeps the dependency off every other platform (musl included),
44
44
  # where pip must see no candidate instead of failing to resolve.
45
- '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")',
45
+ 'simdref-llvm-mc==23.1.4.post1; (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")',
46
46
  "textual>=8.2.8,<9",
47
47
  "typer>=0.27.3,<1",
48
48
  ]
@@ -9,7 +9,6 @@ simdref catalog.
9
9
  from __future__ import annotations
10
10
 
11
11
  import json
12
- import re
13
12
  import sqlite3
14
13
  import statistics
15
14
  import string
@@ -18,6 +17,7 @@ from enum import Enum
18
17
  from typing import Any, Iterable, Iterator
19
18
 
20
19
  from simdref import perf
20
+ from simdref.arm_instructions import _CONDITION_CODES
21
21
  from simdref.asm_identity import isa_by_acceptance
22
22
  from simdref.catkey import ISA_FAMILY, isa_family as _isa_family
23
23
  from simdref.lsp import NO_CATALOG_ROW, _document_arch, identity_keys
@@ -507,11 +507,33 @@ def _lookup_variants(mnemonic: str) -> list[str]:
507
507
 
508
508
 
509
509
  # AArch64 condition-code branches: b.<cond>, no register table covers them.
510
- _A64_BCOND_RE = re.compile(
511
- r"(?<![\w])b\.(?:eq|ne|lt|le|gt|ge|hs|lo|hi|ls|mi|pl|vs|vc|al|nv)(?![\w])"
510
+ # The vocabulary is the first spelling of each cond value in the A64 row table
511
+ # (cs/cc alias the hs/lo spellings the llvm-objdump printers print).
512
+ _A64_BCOND_CODES = frozenset(
513
+ dict(reversed([(cond, code) for code, cond in _CONDITION_CODES])).values()
512
514
  )
513
515
 
514
516
 
517
+ def _bcond_hits(text: str) -> int:
518
+ """Count b.<cond> branch mnemonics with word boundaries on both sides."""
519
+ count = 0
520
+ i = text.find("b.")
521
+ while i >= 0:
522
+ tail = text[i + 2 : i + 4]
523
+ before = text[i - 1 : i]
524
+ after = text[i + 4 : i + 5]
525
+ if (
526
+ tail in _A64_BCOND_CODES
527
+ and not (before and (before.isalnum() or before == "_"))
528
+ and not (after and (after.isalnum() or after == "_"))
529
+ ):
530
+ count += 1
531
+ i = text.find("b.", i + 4)
532
+ else:
533
+ i = text.find("b.", i + 2)
534
+ return count
535
+
536
+
515
537
  def _drop_symbol_annotations(text: str) -> str:
516
538
  """Drop objdump symbol annotations ("<x0+0x50>"): a target, never input."""
517
539
  out: list[str] = []
@@ -646,7 +668,7 @@ def detect_isa(lines: Iterable[str]) -> str | None:
646
668
  # a register (review finding 1).
647
669
  text = _drop_symbol_annotations(f"{parsed.mnemonic} {parsed.operands}")
648
670
  vote_arm, vote_riscv, vote_shared = _isa_votes(text)
649
- excl_arm += vote_arm + len(_A64_BCOND_RE.findall(text))
671
+ excl_arm += vote_arm + _bcond_hits(text)
650
672
  excl_riscv += vote_riscv
651
673
  shared += vote_shared
652
674
  if excl_arm or excl_riscv:
@@ -682,10 +704,36 @@ def lookup(
682
704
  return []
683
705
 
684
706
 
685
- _SIZE_SPECIFIER_RE = re.compile(
686
- r"\b(byte|word|dword|qword|xmmword|ymmword|zmmword)\s+ptr\b",
687
- re.IGNORECASE,
688
- )
707
+ _SIZE_WORDS = frozenset({"byte", "word", "dword", "qword", "xmmword", "ymmword", "zmmword"})
708
+
709
+
710
+ def _has_size_ptr(text: str) -> bool:
711
+ """True for a size keyword with a trailing "ptr" ("byte ptr [rdx]")."""
712
+ n = len(text)
713
+ i = 0
714
+ while i + 3 <= n:
715
+ if text[i : i + 3].lower() != "ptr":
716
+ i += 1
717
+ continue
718
+ if i + 3 < n and (text[i + 3].isalnum() or text[i + 3] == "_"):
719
+ i += 1
720
+ continue
721
+ j = i
722
+ while j > 0 and text[j - 1].isspace():
723
+ j -= 1
724
+ if j == i:
725
+ i += 1
726
+ continue
727
+ for word in _SIZE_WORDS:
728
+ k = j - len(word)
729
+ if (
730
+ k >= 0
731
+ and text[k:j].lower() == word
732
+ and not (k > 0 and (text[k - 1].isalnum() or text[k - 1] == "_"))
733
+ ):
734
+ return True
735
+ i += 1
736
+ return False
689
737
 
690
738
 
691
739
  def _x86_reg_class(name: str) -> str | None:
@@ -750,6 +798,26 @@ def _imm_kind(value: int) -> str:
750
798
  return "I64"
751
799
 
752
800
 
801
+ def _is_number(text: str) -> bool:
802
+ """True for the literal ``-?0x<hex>`` or ``-?<digits>`` the scans matched."""
803
+ body = text[1:] if text[:1] == "-" else text
804
+ if body[:2] == "0x":
805
+ body = body[2:]
806
+ return bool(body) and all(char in string.hexdigits for char in body)
807
+ return bool(body) and body.isdecimal()
808
+
809
+
810
+ def _is_symbol(text: str) -> bool:
811
+ """True for ``[A-Za-z_.$]`` heads with ``\\w.$@`` tails (a plain symbol)."""
812
+ head = text[:1]
813
+ return (
814
+ bool(head)
815
+ and head.isascii()
816
+ and (head.isalpha() or head in "_.$")
817
+ and all(char.isalnum() or char in "_.$@" for char in text[1:])
818
+ )
819
+
820
+
753
821
  def _classify_operand(op: str) -> tuple[str, str | None]:
754
822
  """Return ``(kind, reg_name_lower)`` for one operand.
755
823
 
@@ -763,17 +831,22 @@ def _classify_operand(op: str) -> tuple[str, str | None]:
763
831
  if not s:
764
832
  return "?", None
765
833
  # objdump branch target: an address followed by a <symbol> annotation.
766
- if re.fullmatch(r"-?(?:0x[0-9a-fA-F]+|\d+)\s+<[^>]*>", s):
767
- return "REL", None
834
+ lt = s.find("<")
835
+ if lt > 0 and s.endswith(">") and ">" not in s[lt + 1 : -1]:
836
+ k = lt
837
+ while k > 0 and s[k - 1].isspace():
838
+ k -= 1
839
+ if k < lt and _is_number(s[:k]):
840
+ return "REL", None
768
841
  # AT&T immediate: $... ; Intel immediate: bare number / 0x... / '$'-less literal
769
- if s.startswith("$") or re.fullmatch(r"-?0x[0-9a-fA-F]+|-?\d+", s):
842
+ if s.startswith("$") or _is_number(s):
770
843
  try:
771
844
  return _imm_kind(int(s.lstrip("$"), 0)), None
772
845
  except ValueError:
773
846
  return "I", None
774
847
  # AT&T memory: ``(%reg)``, ``disp(%reg,%idx,scale)``, or plain symbol
775
848
  # with a ``(...)`` section; Intel memory: ``[...]`` or explicit size ptr.
776
- if "(" in s or "[" in s or _SIZE_SPECIFIER_RE.search(s):
849
+ if "(" in s or "[" in s or _has_size_ptr(s):
777
850
  return "M", None
778
851
  # Pure register?
779
852
  name = (s[1:] if s[:1] == "%" else s).casefold()
@@ -781,7 +854,7 @@ def _classify_operand(op: str) -> tuple[str, str | None]:
781
854
  if tok is not None or _is_vec_shaped(name):
782
855
  return tok or "?", name
783
856
  # Plain symbol (branch target) or unknown.
784
- if re.fullmatch(r"[A-Za-z_.$][\w.$@]*", s):
857
+ if _is_symbol(s):
785
858
  return "REL", None
786
859
  return "?", None
787
860
 
@@ -802,9 +875,6 @@ def _operand_shape(operands: str) -> tuple[list[tuple[str, str | None]], bool]:
802
875
  return slots, aliased
803
876
 
804
877
 
805
- _FORM_OPS_RE = re.compile(r"\(([^)]*)\)\s*$")
806
-
807
-
808
878
  def _form_operand_tokens(form_or_key: str) -> list[str]:
809
879
  """Extract the comma-separated operand tokens from a catalog form/key.
810
880
 
@@ -812,14 +882,25 @@ def _form_operand_tokens(form_or_key: str) -> list[str]:
812
882
  ``"RET_NEAR"`` returns ``[]``. Tokens are upper-cased.
813
883
  """
814
884
  s = str(form_or_key or "")
815
- m = _FORM_OPS_RE.search(s)
816
- if not m:
885
+ tail = s.rstrip()
886
+ if not tail or tail[-1] != ")":
817
887
  return []
818
- inner = m.group(1)
888
+ close = len(tail) - 1
889
+ # The inner text starts at the first "(" after the last ")" before the
890
+ # closing one, so no ")" splits it (the old group matched the same span).
891
+ start = tail.find("(", tail.rfind(")", 0, close) + 1)
892
+ if start < 0:
893
+ return []
894
+ inner = tail[start + 1 : close]
819
895
  return [t.strip().upper() for t in inner.split(",") if t.strip()]
820
896
 
821
897
 
822
- _BROADCAST_TOK_RE = re.compile(r"_1TO\d+$")
898
+ def _is_broadcast_token(token: str) -> bool:
899
+ """True for a form token with a ``_1toN`` tail (``M16_1to8``)."""
900
+ start = len(token)
901
+ while start > 0 and token[start - 1].isdecimal():
902
+ start -= 1
903
+ return start < len(token) and start >= 4 and token[start - 4 : start] == "_1TO"
823
904
 
824
905
 
825
906
  def _token_matches_slot(form_tok: str, slot_kind: str) -> bool:
@@ -867,7 +948,7 @@ def _slot_token_score(form_tok: str, slot_kind: str) -> int:
867
948
  if not _token_matches_slot(ft, sk):
868
949
  # Weak near-misses: a broadcast row wants a {1toN} annotation, and a
869
950
  # register can ride a memory row (mov r,r against MOV (M32, R32)).
870
- if sk == "M" and _BROADCAST_TOK_RE.search(ft):
951
+ if sk == "M" and _is_broadcast_token(ft):
871
952
  return 2
872
953
  if sk.startswith("R") and ("M" + sk[1:]) == ft:
873
954
  return 2
@@ -1219,8 +1300,45 @@ def collect_ports(
1219
1300
  return ports or None
1220
1301
 
1221
1302
 
1222
- _SUMMARY_BITS_RE = re.compile(r"(\d+)\s*-?\s*bit", re.IGNORECASE)
1223
- _FORM_WIDTH_RE = re.compile(r"[MRI](\d+)")
1303
+ def _summary_bits(summary: str) -> str | None:
1304
+ """The digits of the first "NNN - bit" phrase ("32-bit" -> "32"), else None.
1305
+
1306
+ shortcut: str.lower folds no Turkish dotted i in "bit", upgrade if an
1307
+ SDM summary ever carries one
1308
+ """
1309
+ for j in range(len(summary) - 2):
1310
+ if summary[j : j + 3].lower() != "bit":
1311
+ continue
1312
+ k = j
1313
+ while k > 0 and summary[k - 1].isspace():
1314
+ k -= 1
1315
+ if k > 0 and summary[k - 1] == "-":
1316
+ k -= 1
1317
+ while k > 0 and summary[k - 1].isspace():
1318
+ k -= 1
1319
+ end = k
1320
+ while k > 0 and summary[k - 1].isdecimal():
1321
+ k -= 1
1322
+ if k < end:
1323
+ return summary[k:end]
1324
+ return None
1325
+
1326
+
1327
+ def _form_widths(key: str) -> list[str]:
1328
+ """The digit groups of the ``M64``-style width tokens of one key."""
1329
+ out: list[str] = []
1330
+ i = 0
1331
+ n = len(key)
1332
+ while i < n:
1333
+ if key[i] in "MRI" and i + 1 < n and key[i + 1].isdecimal():
1334
+ j = i + 1
1335
+ while j < n and key[j].isdecimal():
1336
+ j += 1
1337
+ out.append(key[i + 1 : j])
1338
+ i = j
1339
+ else:
1340
+ i += 1
1341
+ return out
1224
1342
 
1225
1343
 
1226
1344
  def _summary_matches_form(summary: str, record: InstructionRecord) -> bool:
@@ -1229,10 +1347,9 @@ def _summary_matches_form(summary: str, record: InstructionRecord) -> bool:
1229
1347
 
1230
1348
  We only flag a summary as wrong when it declares an explicit bit-width
1231
1349
  that appears nowhere among the form's operand widths."""
1232
- m = _SUMMARY_BITS_RE.search(summary or "")
1233
- if not m:
1350
+ declared = _summary_bits(summary or "")
1351
+ if declared is None:
1234
1352
  return True
1235
- declared = m.group(1)
1236
1353
  widths: list[str] = []
1237
1354
  for op in record.operand_details or []:
1238
1355
  w = op.get("width")
@@ -1240,7 +1357,7 @@ def _summary_matches_form(summary: str, record: InstructionRecord) -> bool:
1240
1357
  widths.append(str(w))
1241
1358
  if not widths:
1242
1359
  key = str(getattr(record, "key", "") or "")
1243
- widths = _FORM_WIDTH_RE.findall(key)
1360
+ widths = _form_widths(key)
1244
1361
  if not widths:
1245
1362
  return True
1246
1363
  return declared in widths
@@ -4,7 +4,6 @@ from __future__ import annotations
4
4
 
5
5
  import html
6
6
  import json
7
- import re
8
7
  from collections import Counter
9
8
  from functools import lru_cache
10
9
  from pathlib import Path
@@ -26,38 +26,139 @@ the platforms it has wheels for; everywhere else point the env vars at a
26
26
 
27
27
  from __future__ import annotations
28
28
 
29
+ import hashlib
29
30
  import json
30
31
  import os
31
32
  import sqlite3
32
33
  import subprocess
33
34
  import tempfile
35
+ from concurrent.futures import ThreadPoolExecutor
34
36
  from dataclasses import dataclass, field
35
37
  from pathlib import Path
36
38
 
37
39
  from simdref.catkey import ISA_FAMILY
38
40
 
39
41
  # Measured invocation of the 144/401/703 corpora probes (asm-pip/probe).
42
+ # The riscv entries carry no "mattr": _mattr_for derives it from the tool.
40
43
  _ARCH_CFG: dict[str, dict] = {
41
- "x86": {
42
- "triple": "x86_64-unknown-linux-gnu",
43
- "mattr": "+avx512f,+avx512bw,+avx512dq,+avx512vl,+avx512vbmi,+avx512vbmi2,"
44
- "+avx512vnni,+avx512bitalg,+avx512vpopcntdq,+avx512ifma,+vaes,+vpclmulqdq,"
45
- "+avx,+avx2,+fma,+f16c,+bmi,+bmi2,+lzcnt,+movbe",
46
- },
47
- "aarch64": {
48
- "triple": "aarch64-unknown-linux-gnu",
49
- "mattr": "+sve,+sve2,+sve2p1,+sme,+sme2,+sme2p1,+lse,+gcs,+aes",
50
- },
44
+ "x86": {"triple": "x86_64-unknown-linux-gnu", "mattr": ""},
45
+ "aarch64": {"triple": "aarch64-unknown-linux-gnu", "mattr": "+all"},
51
46
  "arm": {"triple": "armv7-unknown-linux-gnueabihf", "mattr": "+neon"},
52
- "riscv32": {
53
- "triple": "riscv32-unknown-linux-gnu",
54
- "mattr": "+m,+a,+f,+d,+c,+v,+zba,+zbb,+zbs",
55
- },
56
- "riscv64": {
57
- "triple": "riscv64-unknown-linux-gnu",
58
- "mattr": "+m,+a,+f,+d,+c,+v,+zba,+zbb,+zbs,+zvbb,+zvkb,+zvkned,+zfh",
59
- },
47
+ "riscv32": {"triple": "riscv32-unknown-linux-gnu"},
48
+ "riscv64": {"triple": "riscv64-unknown-linux-gnu"},
60
49
  }
50
+ # The ratified profile each riscv derivation anchors on (spike feat H2).
51
+ _RV_ANCHOR = {"riscv32": "+m,+a,+f,+d,+c,+v", "riscv64": "+rva23u64"}
52
+ _RV_MATTR_CACHE: dict[tuple[str, str], str] = {}
53
+
54
+
55
+ def _rv_help_names(llvm_mc: str, arch: str) -> list[str]:
56
+ """Extension names of ``llvm-mc -mattr=help``, in help order.
57
+
58
+ An extension line has the shape ``name - 'Name' (...)``. A tuning flag
59
+ (fusion-*, reserve-x*) has no quoted name, so the quote check keeps only
60
+ extensions. Vendor (x*) and experimental names drop out: the vendor ones
61
+ reuse the custom opcode spaces."""
62
+ # stdin=DEVNULL: -mattr=help keeps reading stdin, and in the LSP server
63
+ # stdin is the JSON-RPC stream (the derive would eat requests and block).
64
+ run = subprocess.run(
65
+ [llvm_mc, f"--triple={_ARCH_CFG[arch]['triple']}", "-mattr=help"],
66
+ stdin=subprocess.DEVNULL,
67
+ capture_output=True,
68
+ text=True,
69
+ )
70
+ _head, sep, tail = (run.stdout + run.stderr).partition("Available features for this target:")
71
+ names = []
72
+ for line in tail.splitlines() if sep else []:
73
+ left, sep2, _desc = line.strip().partition(" - '")
74
+ name = left.strip()
75
+ if sep2 and name and not any(ch.isspace() for ch in name):
76
+ if not name.startswith(("x", "experimental-")):
77
+ names.append(name)
78
+ return names
79
+
80
+
81
+ def _rv_accepts(llvm_mc: str, arch: str, mattr: str) -> bool:
82
+ """True when llvm-mc assembles one x31 instruction under ``mattr``."""
83
+ run = subprocess.run(
84
+ [llvm_mc, f"--triple={_ARCH_CFG[arch]['triple']}", f"-mattr={mattr}", "-show-encoding"],
85
+ # x31: a base-shrinking entry (e) fails here.
86
+ input="add t6, t6, t6\n",
87
+ capture_output=True,
88
+ text=True,
89
+ )
90
+ return run.returncode == 0 and "encoding" in run.stdout
91
+
92
+
93
+ def _rv_mattr(llvm_mc: str, arch: str) -> str:
94
+ """The riscv ``-mattr``, derived from llvm-mc and cached per tool path.
95
+
96
+ Never a hand list (spike feat H2): probe each non-vendor extension next
97
+ to the ratified anchor and keep what assembles, then keep a name only
98
+ when it also joins the union so far. The anchor decides each
99
+ incompatible pair (F over Zfinx, Zcd over Zcmp), and an extension that
100
+ conflicts with it drops out; a pair conflict can need a third
101
+ extension's implication, so the union test covers that.
102
+
103
+ The cache outlives the process (the derive costs about a second and the
104
+ CLI is one-shot): one file per tool path, size and mtime, so a rebuilt
105
+ tool re-derives."""
106
+ cached = _RV_MATTR_CACHE.get((llvm_mc, arch))
107
+ if cached is not None:
108
+ return cached
109
+ cache_file = _rv_mattr_cache_file(llvm_mc, arch)
110
+ try:
111
+ cached = cache_file.read_text().strip()
112
+ except OSError:
113
+ cached = ""
114
+ if not cached:
115
+ names = _rv_help_names(llvm_mc, arch)
116
+ # Ratified extensions first: on a conflict the earlier one stays.
117
+ names.sort(key=lambda name: name.startswith("experimental-"))
118
+ anchor = _RV_ANCHOR[arch]
119
+ with ThreadPoolExecutor(16) as pool:
120
+ verdict = list(
121
+ pool.map(lambda name: _rv_accepts(llvm_mc, arch, f"{anchor},+{name}"), names)
122
+ )
123
+ kept: list[str] = []
124
+ for name, ok in zip(names, verdict):
125
+ union = ",".join([anchor] + [f"+{k}" for k in kept + [name]])
126
+ if ok and _rv_accepts(llvm_mc, arch, union):
127
+ kept.append(name)
128
+ cached = ",".join([anchor] + [f"+{k}" for k in kept])
129
+ try:
130
+ cache_file.parent.mkdir(parents=True, exist_ok=True)
131
+ tmp = cache_file.with_name(f"{cache_file.name}.tmp{os.getpid()}")
132
+ tmp.write_text(cached)
133
+ os.replace(tmp, cache_file)
134
+ except OSError:
135
+ pass # a read-only cache dir costs one re-derive per process
136
+ _RV_MATTR_CACHE[(llvm_mc, arch)] = cached
137
+ return cached
138
+
139
+
140
+ def _rv_mattr_cache_file(llvm_mc: str, arch: str) -> Path:
141
+ """The cache file for one tool path: keyed by path, size and mtime."""
142
+ try:
143
+ stat = os.stat(llvm_mc)
144
+ stamp = f"{llvm_mc}:{stat.st_size}:{stat.st_mtime_ns}:{arch}"
145
+ except OSError:
146
+ stamp = f"{llvm_mc}:{arch}"
147
+ name = hashlib.sha256(stamp.encode()).hexdigest()[:16]
148
+ base = os.environ.get("XDG_CACHE_HOME") or os.path.join(Path.home(), ".cache")
149
+ return Path(base) / "simdref" / f"rv-mattr-{name}"
150
+
151
+
152
+ def _mattr_for(arch: str, llvm_mc: str) -> str:
153
+ """The ``-mattr`` string for one arch.
154
+
155
+ x86 needs none (its assembler accepts every extension by default) and
156
+ arm keeps its one flag. The riscv string comes from _rv_mattr."""
157
+ if arch.startswith("riscv"):
158
+ return _rv_mattr(llvm_mc, arch)
159
+ return _ARCH_CFG[arch]["mattr"]
160
+
161
+
61
162
  _X86_SYNTAX_FLAG = {"att": "-x86-asm-syntax=att", "intel": "-x86-asm-syntax=intel"}
62
163
  # uops.info eosz attribute values per effective operand size.
63
164
  _EOSZ_ATTR = {16: "1", 32: "2", 64: "3"}
@@ -303,8 +404,9 @@ def _probe_lines_uncached(
303
404
  every other instruction line reports no row (review E1/E2)."""
304
405
  cfg = _ARCH_CFG[arch]
305
406
  cmd = [llvm_mc, f"--triple={cfg['triple']}", "-show-encoding", "-show-inst"]
306
- if mattr or cfg["mattr"]:
307
- cmd.append(f"-mattr={mattr or cfg['mattr']}")
407
+ use = mattr or _mattr_for(arch, llvm_mc)
408
+ if use:
409
+ cmd.append(f"-mattr={use}")
308
410
  if arch == "x86":
309
411
  cmd.append(_X86_SYNTAX_FLAG[syntax])
310
412
 
@@ -441,7 +543,9 @@ def _probe_lines_split(lines: list[str], arch: str, syntax: str, llvm_mc: str) -
441
543
  continue
442
544
  mattr = None
443
545
  if not is_compressed:
444
- mattr = ",".join(t for t in _ARCH_CFG[arch]["mattr"].split(",") if t != "+c")
546
+ # -c,-zca drops every Zc* family (zcb, zcmop, zcd), which the
547
+ # derived set names and a "+c" strip would leave on.
548
+ mattr = _mattr_for(arch, llvm_mc) + ",-c,-zca"
445
549
  sub = _probe_lines([lines[i] for i in indexes], arch, syntax, llvm_mc, mattr=mattr)
446
550
  for index, entry in zip(indexes, sub):
447
551
  merged = dict(entry)
@@ -808,8 +912,9 @@ def _string_spans_lines(lines: list[str], arch: str, syntax: str, llvm_mc: str |
808
912
  raise ToolUnavailableError("llvm-mc not found (set SIMDREF_LLVM_MC)")
809
913
  cfg = _ARCH_CFG[arch]
810
914
  cmd = [llvm_mc, f"--triple={cfg['triple']}"]
811
- if cfg["mattr"]:
812
- cmd.append(f"-mattr={cfg['mattr']}")
915
+ use = _mattr_for(arch, llvm_mc)
916
+ if use:
917
+ cmd.append(f"-mattr={use}")
813
918
  if arch == "x86":
814
919
  cmd.append(_X86_SYNTAX_FLAG[syntax])
815
920
  pending = []
@@ -963,62 +1068,6 @@ def identify(
963
1068
  # whose printed mnemonic equals the line's own text mnemonic (objdump prints
964
1069
  # the expanded or alias name too: jal, flw, sext.w). Both or neither match
965
1070
  # keeps the rv64 result (the current default).
966
- def _rv64_only_ext(line: str) -> str | None:
967
- """The extension name in an "'X' is only supported for 'rv64'" error line.
968
-
969
- Returns None for any other line. The name sits after the last single quote
970
- before the fixed tail, so a prefix like "error: ..." is ignored.
971
- """
972
- head, sep, _tail = line.partition("' is only supported for 'rv64'")
973
- if not sep:
974
- return None
975
- return head.rpartition("'")[2] or None
976
-
977
-
978
- _RV32_MATTR_CACHE: dict[tuple[str, str], str] = {}
979
-
980
-
981
- def _rv32_decode_mattr(llvm_mc: str) -> str:
982
- """The riscv32 decode -mattr: the riscv64 one minus rv64-only extensions.
983
-
984
- Derived from llvm-mc's own errors (never hand-listed): the tool rejects
985
- an rv64-only extension on the rv32 triple by name. shortcut: an extension
986
- named differently in the error keeps its token, upgrade if a tool build
987
- rejects it silently."""
988
- base = _ARCH_CFG["riscv64"]["mattr"]
989
- cached = _RV32_MATTR_CACHE.get((llvm_mc, base))
990
- if cached is None:
991
- tokens = base.split(",")
992
- while True:
993
- run = subprocess.run(
994
- [
995
- llvm_mc,
996
- f"--triple={_ARCH_CFG['riscv32']['triple']}",
997
- f"-mattr={','.join(tokens)}",
998
- "-show-encoding",
999
- ],
1000
- input="",
1001
- capture_output=True,
1002
- text=True,
1003
- )
1004
- drop = {
1005
- name.casefold()
1006
- for line in run.stderr.splitlines()
1007
- for name in [_rv64_only_ext(line)]
1008
- if name
1009
- }
1010
- if not drop:
1011
- break
1012
- remaining = [token for token in tokens if token.lstrip("+").casefold() not in drop]
1013
- # guard: the tool names an extension absent from the token list,
1014
- # so nothing shrank; break here or the loop spins forever
1015
- if len(remaining) == len(tokens):
1016
- break
1017
- tokens = remaining
1018
- cached = _RV32_MATTR_CACHE[(llvm_mc, base)] = ",".join(tokens)
1019
- return cached
1020
-
1021
-
1022
1071
  def _parse_disasm(listing: str) -> dict[bytes, tuple[str, str]]:
1023
1072
  """One (printed mnemonic, MCInst opcode) per decoded word.
1024
1073
 
@@ -1052,7 +1101,7 @@ def _rv_xlen_decodes(
1052
1101
  gate: only an undecided RVC line pays the decode)."""
1053
1102
  missing = [raw for raw in dict.fromkeys(raws) if (llvm_mc, xlen, raw) not in _RV_DECODE_CACHE]
1054
1103
  if missing:
1055
- mattr = _ARCH_CFG["riscv64"]["mattr"] if xlen == 64 else _rv32_decode_mattr(llvm_mc)
1104
+ mattr = _mattr_for(f"riscv{xlen}", llvm_mc)
1056
1105
  hexed = [" ".join(f"0x{byte:02x}" for byte in raw) for raw in missing]
1057
1106
  run = subprocess.run(
1058
1107
  [
@@ -1282,9 +1331,6 @@ def _identify_words(probed, results, catalog: _Catalog, arch: str) -> None:
1282
1331
  _rv_xlen_resolve(probed, results, catalog)
1283
1332
 
1284
1333
 
1285
- _ENDBR_BYTES = {"f30f1efa": "ENDBR64", "f30f1efb": "ENDBR32"}
1286
-
1287
-
1288
1334
  def _identify_x86(probed, results, catalog: _Catalog, xed: str) -> None:
1289
1335
  pending = []
1290
1336
  for entry, result in zip(probed, results):
@@ -1300,7 +1346,7 @@ def _identify_x86(probed, results, catalog: _Catalog, xed: str) -> None:
1300
1346
  if not pending:
1301
1347
  return
1302
1348
  hexes = [_bytes_to_words(entry.get("bytes")).hex() for entry, _result in pending]
1303
- for (entry, result), facts, hexed in zip(pending, _xed_facts(hexes, xed), hexes):
1349
+ for (entry, result), facts in zip(pending, _xed_facts(hexes, xed)):
1304
1350
  # A fixup placeholder covers the immediate value (audit A1): the
1305
1351
  # pick must not read it, and a row carrying an immzero claim about
1306
1352
  # it would be a guess either way.
@@ -1308,8 +1354,7 @@ def _identify_x86(probed, results, catalog: _Catalog, xed: str) -> None:
1308
1354
  if "error" in facts:
1309
1355
  result.reason = f"no catalog row (XED {facts['error']})"
1310
1356
  continue
1311
- # XED decodes ENDBR32/64 as the NOP iform NOP_GPRv_GPRv_0F1E, which no uops.info row carries
1312
- pool = catalog.iform_pools.get(_ENDBR_BYTES.get(hexed, facts["iform"]), [])
1357
+ pool = catalog.iform_pools.get(facts["iform"], [])
1313
1358
  if not pool:
1314
1359
  result.reason = f"no catalog row (iform {facts['iform']} absent)"
1315
1360
  continue