simdref 0.0.16__tar.gz → 0.0.18__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 (179) hide show
  1. {simdref-0.0.16/src/simdref.egg-info → simdref-0.0.18}/PKG-INFO +4 -2
  2. {simdref-0.0.16 → simdref-0.0.18}/README.md +2 -0
  3. {simdref-0.0.16 → simdref-0.0.18}/pyproject.toml +2 -2
  4. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/annotate.py +282 -41
  5. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/arm_instructions.py +0 -1
  6. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/asm_identity.py +166 -86
  7. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/asmline.py +106 -7
  8. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/catkey.py +46 -3
  9. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/cli.py +119 -67
  10. simdref-0.0.18/src/simdref/cuda.py +50 -0
  11. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/display.py +15 -2
  12. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/filters.py +2 -0
  13. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/ingest.py +2 -0
  14. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/ingest_catalog.py +14 -1
  15. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/ingest_sources.py +50 -0
  16. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/lsp.py +214 -29
  17. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/manpages.py +20 -8
  18. simdref-0.0.18/src/simdref/ptx.py +759 -0
  19. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/riscv.py +0 -1
  20. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/storage.py +10 -0
  21. {simdref-0.0.16 → simdref-0.0.18/src/simdref.egg-info}/PKG-INFO +4 -2
  22. {simdref-0.0.16 → simdref-0.0.18}/src/simdref.egg-info/SOURCES.txt +15 -0
  23. {simdref-0.0.16 → simdref-0.0.18}/src/simdref.egg-info/requires.txt +1 -1
  24. simdref-0.0.18/tests/test_annotate_scans.py +52 -0
  25. {simdref-0.0.16 → simdref-0.0.18}/tests/test_asm_wire.py +73 -4
  26. {simdref-0.0.16 → simdref-0.0.18}/tests/test_auto_update_on_version_change.py +1 -0
  27. {simdref-0.0.16 → simdref-0.0.18}/tests/test_catkey.py +4 -0
  28. {simdref-0.0.16 → simdref-0.0.18}/tests/test_cli_bare_query.py +34 -0
  29. simdref-0.0.18/tests/test_cuda_declaration_parser.py +60 -0
  30. simdref-0.0.18/tests/test_cuda_intrinsics.py +109 -0
  31. simdref-0.0.18/tests/test_cuda_td_expansion.py +108 -0
  32. simdref-0.0.18/tests/test_cuda_wiring.py +81 -0
  33. simdref-0.0.18/tests/test_fetch_retry.py +175 -0
  34. {simdref-0.0.16 → simdref-0.0.18}/tests/test_ingest_sources.py +20 -0
  35. simdref-0.0.18/tests/test_issue_65_branch_target.py +91 -0
  36. simdref-0.0.18/tests/test_issue_65_foreign_arch.py +236 -0
  37. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_65_identity.py +40 -15
  38. simdref-0.0.18/tests/test_issue_65_m5.py +22 -0
  39. simdref-0.0.18/tests/test_issue_65_sme_za.py +128 -0
  40. {simdref-0.0.16 → simdref-0.0.18}/tests/test_leftover_arm_form_pick.py +3 -3
  41. simdref-0.0.18/tests/test_lsp_cuda.py +119 -0
  42. simdref-0.0.18/tests/test_lsp_ptx.py +311 -0
  43. {simdref-0.0.16 → simdref-0.0.18}/tests/test_lsp_stdio.py +224 -16
  44. {simdref-0.0.16 → simdref-0.0.18}/tests/test_presentation.py +25 -0
  45. simdref-0.0.18/tests/test_ptx.py +253 -0
  46. {simdref-0.0.16 → simdref-0.0.18}/LICENSE +0 -0
  47. {simdref-0.0.16 → simdref-0.0.18}/setup.cfg +0 -0
  48. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/__init__.py +0 -0
  49. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/__main__.py +0 -0
  50. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/export.py +0 -0
  51. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/htmltext.py +0 -0
  52. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/ingest_pdf.py +0 -0
  53. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/models.py +0 -0
  54. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/operands.py +0 -0
  55. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/pdfparse/__init__.py +0 -0
  56. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/pdfparse/base.py +0 -0
  57. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/pdfparse/intel.py +0 -0
  58. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/pdfparse/registry.py +0 -0
  59. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/pdfparse/types.py +0 -0
  60. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/pdfrefs.py +0 -0
  61. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/perf.py +0 -0
  62. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/perf_sources/__init__.py +0 -0
  63. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/perf_sources/amd_swopt.py +0 -0
  64. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/perf_sources/arm_swopt.py +0 -0
  65. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/perf_sources/cores.py +0 -0
  66. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/perf_sources/instlatx64.py +0 -0
  67. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/perf_sources/intel_guide.py +0 -0
  68. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/perf_sources/intel_optref.py +0 -0
  69. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/perf_sources/llvm_mca.py +0 -0
  70. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
  71. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/perf_sources/merge.py +0 -0
  72. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/queries.py +0 -0
  73. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/regtable.py +0 -0
  74. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/search.py +0 -0
  75. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/tablecols.py +0 -0
  76. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/tui.py +0 -0
  77. {simdref-0.0.16 → simdref-0.0.18}/src/simdref/ui_labels.py +0 -0
  78. {simdref-0.0.16 → simdref-0.0.18}/src/simdref.egg-info/dependency_links.txt +0 -0
  79. {simdref-0.0.16 → simdref-0.0.18}/src/simdref.egg-info/entry_points.txt +0 -0
  80. {simdref-0.0.16 → simdref-0.0.18}/src/simdref.egg-info/top_level.txt +0 -0
  81. {simdref-0.0.16 → simdref-0.0.18}/tests/test_a1_sdm_parser.py +0 -0
  82. {simdref-0.0.16 → simdref-0.0.18}/tests/test_a2_perf_rule.py +0 -0
  83. {simdref-0.0.16 → simdref-0.0.18}/tests/test_a4_arm_pdfrefs.py +0 -0
  84. {simdref-0.0.16 → simdref-0.0.18}/tests/test_a5_offline_aarchmrs.py +0 -0
  85. {simdref-0.0.16 → simdref-0.0.18}/tests/test_amd_match_llvm_mc.py +0 -0
  86. {simdref-0.0.16 → simdref-0.0.18}/tests/test_annotate.py +0 -0
  87. {simdref-0.0.16 → simdref-0.0.18}/tests/test_annotate_positions.py +0 -0
  88. {simdref-0.0.16 → simdref-0.0.18}/tests/test_arrangement_tail.py +0 -0
  89. {simdref-0.0.16 → simdref-0.0.18}/tests/test_asm_identity.py +0 -0
  90. {simdref-0.0.16 → simdref-0.0.18}/tests/test_asmline.py +0 -0
  91. {simdref-0.0.16 → simdref-0.0.18}/tests/test_audit_coverage.py +0 -0
  92. {simdref-0.0.16 → simdref-0.0.18}/tests/test_b1_latency_pairs.py +0 -0
  93. {simdref-0.0.16 → simdref-0.0.18}/tests/test_b2_metadata.py +0 -0
  94. {simdref-0.0.16 → simdref-0.0.18}/tests/test_b3_sdm_forms.py +0 -0
  95. {simdref-0.0.16 → simdref-0.0.18}/tests/test_b4_descriptions_provenance.py +0 -0
  96. {simdref-0.0.16 → simdref-0.0.18}/tests/test_b5_linking.py +0 -0
  97. {simdref-0.0.16 → simdref-0.0.18}/tests/test_b5_perf_family.py +0 -0
  98. {simdref-0.0.16 → simdref-0.0.18}/tests/test_b5_sme.py +0 -0
  99. {simdref-0.0.16 → simdref-0.0.18}/tests/test_c0_perf_priority.py +0 -0
  100. {simdref-0.0.16 → simdref-0.0.18}/tests/test_cli_bootstrap_progress.py +0 -0
  101. {simdref-0.0.16 → simdref-0.0.18}/tests/test_cli_help.py +0 -0
  102. {simdref-0.0.16 → simdref-0.0.18}/tests/test_cli_llm.py +0 -0
  103. {simdref-0.0.16 → simdref-0.0.18}/tests/test_coverage_live.py +0 -0
  104. {simdref-0.0.16 → simdref-0.0.18}/tests/test_coverage_parity.py +0 -0
  105. {simdref-0.0.16 → simdref-0.0.18}/tests/test_display.py +0 -0
  106. {simdref-0.0.16 → simdref-0.0.18}/tests/test_display_scanners.py +0 -0
  107. {simdref-0.0.16 → simdref-0.0.18}/tests/test_export.py +0 -0
  108. {simdref-0.0.16 → simdref-0.0.18}/tests/test_export_viz.py +0 -0
  109. {simdref-0.0.16 → simdref-0.0.18}/tests/test_filters.py +0 -0
  110. {simdref-0.0.16 → simdref-0.0.18}/tests/test_fixui_display.py +0 -0
  111. {simdref-0.0.16 → simdref-0.0.18}/tests/test_htmltext.py +0 -0
  112. {simdref-0.0.16 → simdref-0.0.18}/tests/test_ingest_enc_fields.py +0 -0
  113. {simdref-0.0.16 → simdref-0.0.18}/tests/test_int_arm_isa_label.py +0 -0
  114. {simdref-0.0.16 → simdref-0.0.18}/tests/test_int_core_ids.py +0 -0
  115. {simdref-0.0.16 → simdref-0.0.18}/tests/test_int_ingest_registration.py +0 -0
  116. {simdref-0.0.16 → simdref-0.0.18}/tests/test_int_sdm_family_fallback.py +0 -0
  117. {simdref-0.0.16 → simdref-0.0.18}/tests/test_intel_operation_and_url.py +0 -0
  118. {simdref-0.0.16 → simdref-0.0.18}/tests/test_isa_detect_llvm_mc.py +0 -0
  119. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue2_fixes.py +0 -0
  120. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_51.py +0 -0
  121. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_52.py +0 -0
  122. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_53_54.py +0 -0
  123. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_55.py +0 -0
  124. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_56_57a.py +0 -0
  125. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_57_cpi.py +0 -0
  126. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_60_c1.py +0 -0
  127. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_60_contract.py +0 -0
  128. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_60_crossarch.py +0 -0
  129. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_60_matcher.py +0 -0
  130. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_65_arm_alias.py +0 -0
  131. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_65_compressed.py +0 -0
  132. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_65_m1.py +0 -0
  133. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_65_perfdata.py +0 -0
  134. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_65_text.py +0 -0
  135. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issue_65_timing.py +0 -0
  136. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issues_23_24_25.py +0 -0
  137. {simdref-0.0.16 → simdref-0.0.18}/tests/test_issues_51_57_join.py +0 -0
  138. {simdref-0.0.16 → simdref-0.0.18}/tests/test_leftover_arm_postindex.py +0 -0
  139. {simdref-0.0.16 → simdref-0.0.18}/tests/test_leftover_att_movx.py +0 -0
  140. {simdref-0.0.16 → simdref-0.0.18}/tests/test_leftover_autoupdate.py +0 -0
  141. {simdref-0.0.16 → simdref-0.0.18}/tests/test_leftover_catalog_gaps.py +0 -0
  142. {simdref-0.0.16 → simdref-0.0.18}/tests/test_leftover_ingest_isa_merge.py +0 -0
  143. {simdref-0.0.16 → simdref-0.0.18}/tests/test_leftover_intel_size.py +0 -0
  144. {simdref-0.0.16 → simdref-0.0.18}/tests/test_leftover_perf_join.py +0 -0
  145. {simdref-0.0.16 → simdref-0.0.18}/tests/test_leftover_riscv_shared.py +0 -0
  146. {simdref-0.0.16 → simdref-0.0.18}/tests/test_lsp_web.py +0 -0
  147. {simdref-0.0.16 → simdref-0.0.18}/tests/test_models.py +0 -0
  148. {simdref-0.0.16 → simdref-0.0.18}/tests/test_no_catalog_row.py +0 -0
  149. {simdref-0.0.16 → simdref-0.0.18}/tests/test_objdump_listings.py +0 -0
  150. {simdref-0.0.16 → simdref-0.0.18}/tests/test_operands.py +0 -0
  151. {simdref-0.0.16 → simdref-0.0.18}/tests/test_pdfparse.py +0 -0
  152. {simdref-0.0.16 → simdref-0.0.18}/tests/test_perf.py +0 -0
  153. {simdref-0.0.16 → simdref-0.0.18}/tests/test_perf_source_amd_swopt.py +0 -0
  154. {simdref-0.0.16 → simdref-0.0.18}/tests/test_perf_source_arm_swopt.py +0 -0
  155. {simdref-0.0.16 → simdref-0.0.18}/tests/test_perf_source_instlatx64.py +0 -0
  156. {simdref-0.0.16 → simdref-0.0.18}/tests/test_perf_source_intel_guide.py +0 -0
  157. {simdref-0.0.16 → simdref-0.0.18}/tests/test_perf_source_intel_optref.py +0 -0
  158. {simdref-0.0.16 → simdref-0.0.18}/tests/test_perf_sources.py +0 -0
  159. {simdref-0.0.16 → simdref-0.0.18}/tests/test_preset_persistence.py +0 -0
  160. {simdref-0.0.16 → simdref-0.0.18}/tests/test_regtable.py +0 -0
  161. {simdref-0.0.16 → simdref-0.0.18}/tests/test_release_tag_pin.py +0 -0
  162. {simdref-0.0.16 → simdref-0.0.18}/tests/test_riscv_docs_pages.py +0 -0
  163. {simdref-0.0.16 → simdref-0.0.18}/tests/test_search.py +0 -0
  164. {simdref-0.0.16 → simdref-0.0.18}/tests/test_search_pushdown.py +0 -0
  165. {simdref-0.0.16 → simdref-0.0.18}/tests/test_shared_source_helpers.py +0 -0
  166. {simdref-0.0.16 → simdref-0.0.18}/tests/test_source_kind_filter.py +0 -0
  167. {simdref-0.0.16 → simdref-0.0.18}/tests/test_source_validation.py +0 -0
  168. {simdref-0.0.16 → simdref-0.0.18}/tests/test_storage_alnum.py +0 -0
  169. {simdref-0.0.16 → simdref-0.0.18}/tests/test_storage_payload.py +0 -0
  170. {simdref-0.0.16 → simdref-0.0.18}/tests/test_storage_schema.py +0 -0
  171. {simdref-0.0.16 → simdref-0.0.18}/tests/test_tablecols.py +0 -0
  172. {simdref-0.0.16 → simdref-0.0.18}/tests/test_tui.py +0 -0
  173. {simdref-0.0.16 → simdref-0.0.18}/tests/test_ui_labels_parity.py +0 -0
  174. {simdref-0.0.16 → simdref-0.0.18}/tests/test_update_build_stamp.py +0 -0
  175. {simdref-0.0.16 → simdref-0.0.18}/tests/test_vendor_riscv_categories.py +0 -0
  176. {simdref-0.0.16 → simdref-0.0.18}/tests/test_vendor_riscv_sources.py +0 -0
  177. {simdref-0.0.16 → simdref-0.0.18}/tests/test_version_matches_pyproject.py +0 -0
  178. {simdref-0.0.16 → simdref-0.0.18}/tests/test_x86_key_widths.py +0 -0
  179. {simdref-0.0.16 → simdref-0.0.18}/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.18
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,8 @@ 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 | 478 instructions |
181
+ | CUDA intrinsics | Device intrinsics from clang CUDA headers | 585 intrinsics |
180
182
  | Intel Guide perf tables | Per-core latency and throughput | 9,134 rows |
181
183
  | Intel Optimization Ref. | Latency and throughput tables (volume 2) | 3,155 rows |
182
184
  | InstLatX64 | Measured latency and throughput dumps | 16,235 rows |
@@ -142,6 +142,8 @@ 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 | 478 instructions |
146
+ | CUDA intrinsics | Device intrinsics from clang CUDA headers | 585 intrinsics |
145
147
  | Intel Guide perf tables | Per-core latency and throughput | 9,134 rows |
146
148
  | Intel Optimization Ref. | Latency and throughput tables (volume 2) | 3,155 rows |
147
149
  | 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.18"
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,9 +17,19 @@ 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
- from simdref.catkey import ISA_FAMILY, isa_family as _isa_family
23
- from simdref.lsp import NO_CATALOG_ROW, _document_arch, identity_keys
22
+ from simdref.catkey import (
23
+ IMMEDIATE_ACCEPT,
24
+ ISA_FAMILY,
25
+ LITERAL_ACCEPT,
26
+ form_accept,
27
+ isa_family as _isa_family,
28
+ key_names_za_tile,
29
+ names_za_tile,
30
+ operands_of_key,
31
+ )
32
+ from simdref.lsp import NO_CATALOG_ROW, _classify_line, _document_arch, identity_keys
24
33
  from simdref.models import InstructionRecord
25
34
  from simdref.regtable import REG_TABLE, reg_rows
26
35
  from simdref.storage import load_instruction_from_db, load_instructions_by_mnemonic_from_db
@@ -471,7 +480,8 @@ def _asm_line(stripped: str, obj: _ObjdumpRecord | None, track_positions: bool)
471
480
  return AsmLine(LineKind.LABEL, stripped)
472
481
 
473
482
  head, comment = _split_comment(stripped)
474
- m = _instr_head(head)
483
+ indent = head[: len(head) - len(head.lstrip())]
484
+ m = _instr_head(indent + _strip_prefix_tokens(head))
475
485
  if not m:
476
486
  return AsmLine(LineKind.COMMENT, stripped)
477
487
  return AsmLine(
@@ -507,11 +517,33 @@ def _lookup_variants(mnemonic: str) -> list[str]:
507
517
 
508
518
 
509
519
  # 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])"
520
+ # The vocabulary is the first spelling of each cond value in the A64 row table
521
+ # (cs/cc alias the hs/lo spellings the llvm-objdump printers print).
522
+ _A64_BCOND_CODES = frozenset(
523
+ dict(reversed([(cond, code) for code, cond in _CONDITION_CODES])).values()
512
524
  )
513
525
 
514
526
 
527
+ def _bcond_hits(text: str) -> int:
528
+ """Count b.<cond> branch mnemonics with word boundaries on both sides."""
529
+ count = 0
530
+ i = text.find("b.")
531
+ while i >= 0:
532
+ tail = text[i + 2 : i + 4]
533
+ before = text[i - 1 : i]
534
+ after = text[i + 4 : i + 5]
535
+ if (
536
+ tail in _A64_BCOND_CODES
537
+ and not (before and (before.isalnum() or before == "_"))
538
+ and not (after and (after.isalnum() or after == "_"))
539
+ ):
540
+ count += 1
541
+ i = text.find("b.", i + 4)
542
+ else:
543
+ i = text.find("b.", i + 2)
544
+ return count
545
+
546
+
515
547
  def _drop_symbol_annotations(text: str) -> str:
516
548
  """Drop objdump symbol annotations ("<x0+0x50>"): a target, never input."""
517
549
  out: list[str] = []
@@ -594,6 +626,42 @@ def _voter_names():
594
626
 
595
627
  _EXCL_ARM, _SHARED, _RISCV = _voter_names()
596
628
 
629
+ # The register families simdref supports: REG_TABLE also carries ARM32 spellings.
630
+ _SUPPORTED_FAMILIES = frozenset({"x86", "a64", "riscv"})
631
+
632
+
633
+ def _foreign_operands(operands: str) -> bool:
634
+ """True when the operands parse as no supported arch's operands.
635
+
636
+ A ``$``-sigil register (the MIPS/PPC spelling) belongs to no supported
637
+ arch, and a line whose register words all name unsupported families
638
+ (ARM32's r0-r15, ip, pc) carries no supported evidence. A word absent
639
+ from REG_TABLE is a symbol and stays neutral, and one supported-family
640
+ register anywhere clears the whole line (issue #65 B2/B6).
641
+ """
642
+ unsupported = sigil = False
643
+ i = 0
644
+ n = len(operands)
645
+ while i < n:
646
+ if not (operands[i] == "_" or operands[i].isalnum()):
647
+ i += 1
648
+ continue
649
+ j = i
650
+ while j < n and (operands[j] == "_" or operands[j].isalnum()):
651
+ j += 1
652
+ if i > 0 and operands[i - 1] == "$" and not operands[i].isdigit():
653
+ sigil = True
654
+ else:
655
+ families = {
656
+ family for _klass, _width, family in REG_TABLE.get(operands[i:j].casefold(), ())
657
+ }
658
+ if families & _SUPPORTED_FAMILIES:
659
+ return False
660
+ if families:
661
+ unsupported = True
662
+ i = j
663
+ return sigil or unsupported
664
+
597
665
 
598
666
  def _isa_votes(text: str) -> tuple[int, int, int]:
599
667
  """(excl_arm, excl_riscv, shared) register votes of one instruction text."""
@@ -608,7 +676,9 @@ def _isa_votes(text: str) -> tuple[int, int, int]:
608
676
  while j < n and (text[j] == "_" or text[j].isalnum()):
609
677
  j += 1
610
678
  token = text[i:j]
611
- if i == 0 or text[i - 1] != "%":
679
+ # '%' starts an AT&T register and '$' a sigil register of no
680
+ # supported arch (MIPS/PPC): the name inside never votes (issue #65).
681
+ if i == 0 or text[i - 1] not in "%$":
612
682
  if token in _EXCL_ARM:
613
683
  excl_arm += 1
614
684
  elif token in _SHARED:
@@ -646,7 +716,7 @@ def detect_isa(lines: Iterable[str]) -> str | None:
646
716
  # a register (review finding 1).
647
717
  text = _drop_symbol_annotations(f"{parsed.mnemonic} {parsed.operands}")
648
718
  vote_arm, vote_riscv, vote_shared = _isa_votes(text)
649
- excl_arm += vote_arm + len(_A64_BCOND_RE.findall(text))
719
+ excl_arm += vote_arm + _bcond_hits(text)
650
720
  excl_riscv += vote_riscv
651
721
  shared += vote_shared
652
722
  if excl_arm or excl_riscv:
@@ -682,10 +752,36 @@ def lookup(
682
752
  return []
683
753
 
684
754
 
685
- _SIZE_SPECIFIER_RE = re.compile(
686
- r"\b(byte|word|dword|qword|xmmword|ymmword|zmmword)\s+ptr\b",
687
- re.IGNORECASE,
688
- )
755
+ _SIZE_WORDS = frozenset({"byte", "word", "dword", "qword", "xmmword", "ymmword", "zmmword"})
756
+
757
+
758
+ def _has_size_ptr(text: str) -> bool:
759
+ """True for a size keyword with a trailing "ptr" ("byte ptr [rdx]")."""
760
+ n = len(text)
761
+ i = 0
762
+ while i + 3 <= n:
763
+ if text[i : i + 3].lower() != "ptr":
764
+ i += 1
765
+ continue
766
+ if i + 3 < n and (text[i + 3].isalnum() or text[i + 3] == "_"):
767
+ i += 1
768
+ continue
769
+ j = i
770
+ while j > 0 and text[j - 1].isspace():
771
+ j -= 1
772
+ if j == i:
773
+ i += 1
774
+ continue
775
+ for word in _SIZE_WORDS:
776
+ k = j - len(word)
777
+ if (
778
+ k >= 0
779
+ and text[k:j].lower() == word
780
+ and not (k > 0 and (text[k - 1].isalnum() or text[k - 1] == "_"))
781
+ ):
782
+ return True
783
+ i += 1
784
+ return False
689
785
 
690
786
 
691
787
  def _x86_reg_class(name: str) -> str | None:
@@ -750,6 +846,26 @@ def _imm_kind(value: int) -> str:
750
846
  return "I64"
751
847
 
752
848
 
849
+ def _is_number(text: str) -> bool:
850
+ """True for the literal ``-?0x<hex>`` or ``-?<digits>`` the scans matched."""
851
+ body = text[1:] if text[:1] == "-" else text
852
+ if body[:2] == "0x":
853
+ body = body[2:]
854
+ return bool(body) and all(char in string.hexdigits for char in body)
855
+ return bool(body) and body.isdecimal()
856
+
857
+
858
+ def _is_symbol(text: str) -> bool:
859
+ """True for ``[A-Za-z_.$]`` heads with ``\\w.$@`` tails (a plain symbol)."""
860
+ head = text[:1]
861
+ return (
862
+ bool(head)
863
+ and head.isascii()
864
+ and (head.isalpha() or head in "_.$")
865
+ and all(char.isalnum() or char in "_.$@" for char in text[1:])
866
+ )
867
+
868
+
753
869
  def _classify_operand(op: str) -> tuple[str, str | None]:
754
870
  """Return ``(kind, reg_name_lower)`` for one operand.
755
871
 
@@ -763,17 +879,22 @@ def _classify_operand(op: str) -> tuple[str, str | None]:
763
879
  if not s:
764
880
  return "?", None
765
881
  # 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
882
+ lt = s.find("<")
883
+ if lt > 0 and s.endswith(">") and ">" not in s[lt + 1 : -1]:
884
+ k = lt
885
+ while k > 0 and s[k - 1].isspace():
886
+ k -= 1
887
+ if k < lt and _is_number(s[:k]):
888
+ return "REL", None
768
889
  # 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):
890
+ if s.startswith("$") or _is_number(s):
770
891
  try:
771
892
  return _imm_kind(int(s.lstrip("$"), 0)), None
772
893
  except ValueError:
773
894
  return "I", None
774
895
  # AT&T memory: ``(%reg)``, ``disp(%reg,%idx,scale)``, or plain symbol
775
896
  # with a ``(...)`` section; Intel memory: ``[...]`` or explicit size ptr.
776
- if "(" in s or "[" in s or _SIZE_SPECIFIER_RE.search(s):
897
+ if "(" in s or "[" in s or _has_size_ptr(s):
777
898
  return "M", None
778
899
  # Pure register?
779
900
  name = (s[1:] if s[:1] == "%" else s).casefold()
@@ -781,7 +902,7 @@ def _classify_operand(op: str) -> tuple[str, str | None]:
781
902
  if tok is not None or _is_vec_shaped(name):
782
903
  return tok or "?", name
783
904
  # Plain symbol (branch target) or unknown.
784
- if re.fullmatch(r"[A-Za-z_.$][\w.$@]*", s):
905
+ if _is_symbol(s):
785
906
  return "REL", None
786
907
  return "?", None
787
908
 
@@ -802,9 +923,6 @@ def _operand_shape(operands: str) -> tuple[list[tuple[str, str | None]], bool]:
802
923
  return slots, aliased
803
924
 
804
925
 
805
- _FORM_OPS_RE = re.compile(r"\(([^)]*)\)\s*$")
806
-
807
-
808
926
  def _form_operand_tokens(form_or_key: str) -> list[str]:
809
927
  """Extract the comma-separated operand tokens from a catalog form/key.
810
928
 
@@ -812,14 +930,25 @@ def _form_operand_tokens(form_or_key: str) -> list[str]:
812
930
  ``"RET_NEAR"`` returns ``[]``. Tokens are upper-cased.
813
931
  """
814
932
  s = str(form_or_key or "")
815
- m = _FORM_OPS_RE.search(s)
816
- if not m:
933
+ tail = s.rstrip()
934
+ if not tail or tail[-1] != ")":
935
+ return []
936
+ close = len(tail) - 1
937
+ # The inner text starts at the first "(" after the last ")" before the
938
+ # closing one, so no ")" splits it (the old group matched the same span).
939
+ start = tail.find("(", tail.rfind(")", 0, close) + 1)
940
+ if start < 0:
817
941
  return []
818
- inner = m.group(1)
942
+ inner = tail[start + 1 : close]
819
943
  return [t.strip().upper() for t in inner.split(",") if t.strip()]
820
944
 
821
945
 
822
- _BROADCAST_TOK_RE = re.compile(r"_1TO\d+$")
946
+ def _is_broadcast_token(token: str) -> bool:
947
+ """True for a form token with a ``_1toN`` tail (``M16_1to8``)."""
948
+ start = len(token)
949
+ while start > 0 and token[start - 1].isdecimal():
950
+ start -= 1
951
+ return start < len(token) and start >= 4 and token[start - 4 : start] == "_1TO"
823
952
 
824
953
 
825
954
  def _token_matches_slot(form_tok: str, slot_kind: str) -> bool:
@@ -867,7 +996,7 @@ def _slot_token_score(form_tok: str, slot_kind: str) -> int:
867
996
  if not _token_matches_slot(ft, sk):
868
997
  # Weak near-misses: a broadcast row wants a {1toN} annotation, and a
869
998
  # register can ride a memory row (mov r,r against MOV (M32, R32)).
870
- if sk == "M" and _BROADCAST_TOK_RE.search(ft):
999
+ if sk == "M" and _is_broadcast_token(ft):
871
1000
  return 2
872
1001
  if sk.startswith("R") and ("M" + sk[1:]) == ft:
873
1002
  return 2
@@ -966,6 +1095,19 @@ def pick_record(
966
1095
  # AT&T lists sources first; catalog form tokens are in Intel order.
967
1096
  slots = list(reversed(slots))
968
1097
 
1098
+ # A ZA tile operand names the SME tile rows only, and a line without one
1099
+ # never wants them (issue #65 M11): the rows share the mnemonic and
1100
+ # operand count with the plain SVE/NEON forms, so operand shape alone
1101
+ # picks a timed plain row where the ZA forms carry no timing. Same parity
1102
+ # rule as the "_1to" broadcast filter in asm_identity.
1103
+ za_pair = [
1104
+ record
1105
+ for record in candidates
1106
+ if key_names_za_tile(str(getattr(record, "key", "") or "")) == names_za_tile(operands)
1107
+ ]
1108
+ if za_pair:
1109
+ candidates = za_pair
1110
+
969
1111
  def score(rec: InstructionRecord) -> tuple[int, int, int, int]:
970
1112
  measured = sum(
971
1113
  1
@@ -982,6 +1124,49 @@ def pick_record(
982
1124
  return max(candidates, key=score)
983
1125
 
984
1126
 
1127
+ def _record_takes_line(record: InstructionRecord, operands: str) -> bool:
1128
+ """Whether the record's key accepts the line's operand classes.
1129
+
1130
+ Classes and accepts come from the catalog: the lsp line classifier and
1131
+ catkey's accept sets. A bare key constrains nothing, a literal key operand
1132
+ (the zero-idiom rows) constrains nothing, and an input the classifier
1133
+ gave no class constrains nothing. Register and memory classes ride each
1134
+ other here exactly like the form pick, so the only hard mismatch is an
1135
+ immediate input against a key operand that accepts no immediate: on an
1136
+ undecided line that pick is a foreign borrow, not an answer (a bare
1137
+ "1, 2, 3" never names a register-class row, issue #65 B2). A decided
1138
+ line named its family already, so only the undecided line is judged.
1139
+ """
1140
+ ops = operands_of_key(record.key)
1141
+ if not ops:
1142
+ return True
1143
+ arch, classes, _reverse, _weak = _classify_line(operands, False)
1144
+ if arch:
1145
+ # A decided line named its family; the family-pinned pick stands.
1146
+ return True
1147
+ if "%" in operands or "$" in operands:
1148
+ # AT&T lists sources first and key operands are in Intel order, so
1149
+ # the classes flip to meet the key (the rule pick_record's slots
1150
+ # use). A sigil-less line keeps its order: Intel and ARM both name
1151
+ # the destination first, as the key does.
1152
+ classes = list(reversed(classes))
1153
+ immediates = [cls for cls in classes if cls in ("i", "lit0", "lit1")]
1154
+ if not immediates:
1155
+ return True
1156
+ if len(ops) != len(classes):
1157
+ return False
1158
+ for cls, op in zip(classes, ops):
1159
+ accept = form_accept(op)
1160
+ if (
1161
+ cls in ("i", "lit0", "lit1")
1162
+ and accept
1163
+ and accept not in LITERAL_ACCEPT
1164
+ and accept.isdisjoint(IMMEDIATE_ACCEPT)
1165
+ ):
1166
+ return False
1167
+ return True
1168
+
1169
+
985
1170
  # ---------------------------------------------------------------------------
986
1171
  # Aggregation
987
1172
  # ---------------------------------------------------------------------------
@@ -1219,8 +1404,45 @@ def collect_ports(
1219
1404
  return ports or None
1220
1405
 
1221
1406
 
1222
- _SUMMARY_BITS_RE = re.compile(r"(\d+)\s*-?\s*bit", re.IGNORECASE)
1223
- _FORM_WIDTH_RE = re.compile(r"[MRI](\d+)")
1407
+ def _summary_bits(summary: str) -> str | None:
1408
+ """The digits of the first "NNN - bit" phrase ("32-bit" -> "32"), else None.
1409
+
1410
+ shortcut: str.lower folds no Turkish dotted i in "bit", upgrade if an
1411
+ SDM summary ever carries one
1412
+ """
1413
+ for j in range(len(summary) - 2):
1414
+ if summary[j : j + 3].lower() != "bit":
1415
+ continue
1416
+ k = j
1417
+ while k > 0 and summary[k - 1].isspace():
1418
+ k -= 1
1419
+ if k > 0 and summary[k - 1] == "-":
1420
+ k -= 1
1421
+ while k > 0 and summary[k - 1].isspace():
1422
+ k -= 1
1423
+ end = k
1424
+ while k > 0 and summary[k - 1].isdecimal():
1425
+ k -= 1
1426
+ if k < end:
1427
+ return summary[k:end]
1428
+ return None
1429
+
1430
+
1431
+ def _form_widths(key: str) -> list[str]:
1432
+ """The digit groups of the ``M64``-style width tokens of one key."""
1433
+ out: list[str] = []
1434
+ i = 0
1435
+ n = len(key)
1436
+ while i < n:
1437
+ if key[i] in "MRI" and i + 1 < n and key[i + 1].isdecimal():
1438
+ j = i + 1
1439
+ while j < n and key[j].isdecimal():
1440
+ j += 1
1441
+ out.append(key[i + 1 : j])
1442
+ i = j
1443
+ else:
1444
+ i += 1
1445
+ return out
1224
1446
 
1225
1447
 
1226
1448
  def _summary_matches_form(summary: str, record: InstructionRecord) -> bool:
@@ -1229,10 +1451,9 @@ def _summary_matches_form(summary: str, record: InstructionRecord) -> bool:
1229
1451
 
1230
1452
  We only flag a summary as wrong when it declares an explicit bit-width
1231
1453
  that appears nowhere among the form's operand widths."""
1232
- m = _SUMMARY_BITS_RE.search(summary or "")
1233
- if not m:
1454
+ declared = _summary_bits(summary or "")
1455
+ if declared is None:
1234
1456
  return True
1235
- declared = m.group(1)
1236
1457
  widths: list[str] = []
1237
1458
  for op in record.operand_details or []:
1238
1459
  w = op.get("width")
@@ -1240,7 +1461,7 @@ def _summary_matches_form(summary: str, record: InstructionRecord) -> bool:
1240
1461
  widths.append(str(w))
1241
1462
  if not widths:
1242
1463
  key = str(getattr(record, "key", "") or "")
1243
- widths = _FORM_WIDTH_RE.findall(key)
1464
+ widths = _form_widths(key)
1244
1465
  if not widths:
1245
1466
  return True
1246
1467
  return declared in widths
@@ -1322,12 +1543,15 @@ def _annotate_instruction(
1322
1543
  line_isa: str | None = None,
1323
1544
  line_number: int | None = None,
1324
1545
  identity_cache: dict[str, tuple[InstructionRecord, PerfSummary | None]] | None = None,
1546
+ foreign: bool = False,
1325
1547
  ) -> tuple[str, dict[str, Any] | None]:
1326
1548
  """Return the rendered output line and an optional JSON record.
1327
1549
 
1328
1550
  ``line_isa`` names the family the line's own evidence shows (a pinned
1329
1551
  ``--isa`` counts as one). None marks a neutral line in auto mode: the
1330
- stream family answers first, then the family that holds rows.
1552
+ stream family answers first, then the family that holds rows. ``foreign``
1553
+ marks a line whose operands parse as no supported arch's operands
1554
+ (PPC, MIPS, ARM32): no supported family's rows answer it (issue #65 B2/B6).
1331
1555
  """
1332
1556
  if stats is not None:
1333
1557
  stats["parsed"] = stats.get("parsed", 0) + 1
@@ -1392,7 +1616,9 @@ def _annotate_instruction(
1392
1616
  else:
1393
1617
  isa = line_isa or opts.isa
1394
1618
  pick_isa = isa
1395
- if lookup_cache is not None:
1619
+ if foreign:
1620
+ records = []
1621
+ elif lookup_cache is not None:
1396
1622
  records = lookup_cache.get((parsed.mnemonic, isa))
1397
1623
  if records is None:
1398
1624
  # Module-level call (not a captured reference) so tests can
@@ -1401,16 +1627,21 @@ def _annotate_instruction(
1401
1627
  lookup_cache[(parsed.mnemonic, isa)] = records
1402
1628
  else:
1403
1629
  records = lookup(parsed.mnemonic, conn, isa=isa)
1404
- if not records and line_isa is None:
1630
+ if not records and line_isa is None and not foreign:
1405
1631
  # A neutral line whose stream family holds no rows gets the rows
1406
1632
  # of the family that has them (issue #57 batch: jnle, bare b).
1633
+ # A foreign line borrows nothing (issue #65 B2/B6).
1407
1634
  pick_isa = None
1408
1635
  records = lookup(parsed.mnemonic, conn, isa=None)
1409
1636
  if lookup_cache is not None:
1410
1637
  lookup_cache[(parsed.mnemonic, None)] = records
1411
- if stats is not None and records:
1412
- stats["recognized"] = stats.get("recognized", 0) + 1
1413
1638
  record = pick_record(records, arch=opts.arch, operands=parsed.operands, isa=pick_isa)
1639
+ if record is not None and not _record_takes_line(record, parsed.operands):
1640
+ # The key does not take the line's operand classes: a foreign
1641
+ # borrow, not an answer (issue #65 B2).
1642
+ record = None
1643
+ if stats is not None and record is not None:
1644
+ stats["recognized"] = stats.get("recognized", 0) + 1
1414
1645
 
1415
1646
  if record is None:
1416
1647
  if opts.unknown == "drop":
@@ -1569,6 +1800,7 @@ def annotate_stream(
1569
1800
  }
1570
1801
  auto_isa = opts.isa not in ("x86", "arm", "riscv")
1571
1802
  line_family: dict[int, str] = {}
1803
+ foreign: set[int] = set()
1572
1804
  if auto_isa:
1573
1805
  # Assembler acceptance decides each line's family: one llvm-mc run
1574
1806
  # per candidate triple. The register vote below is the fallback when
@@ -1576,6 +1808,13 @@ def annotate_stream(
1576
1808
  probe_texts = [text for _index, text in items]
1577
1809
  families, majority = isa_by_acceptance(probe_texts)
1578
1810
  line_family = {index: family for (index, _text), family in zip(items, families) if family}
1811
+ # The line's own parse names foreign architectures (PPC/MIPS/ARM32):
1812
+ # those lines borrow no rows from a supported family (issue #65 B2/B6).
1813
+ foreign = {
1814
+ index
1815
+ for index, parsed in enumerate(parsed_lines)
1816
+ if parsed.kind == LineKind.INSTRUCTION and _foreign_operands(parsed.operands)
1817
+ }
1579
1818
  # auto: the acceptance majority names the ISA, then the input
1580
1819
  # signals, then the document arch from _document_arch; x86 stays
1581
1820
  # the guess when all stay silent.
@@ -1612,6 +1851,11 @@ def annotate_stream(
1612
1851
  if not collecting_json:
1613
1852
  yield parsed.raw + "\n"
1614
1853
  continue
1854
+ line_isa = opts.isa
1855
+ if auto_isa:
1856
+ # A pin forces one family; auto mode splits the batch per line,
1857
+ # the assembler verdict first and the register vote as fallback.
1858
+ line_isa = line_family.get(index) or _line_isa(f"{parsed.mnemonic} {parsed.operands}")
1615
1859
  out_line, record = _annotate_instruction(
1616
1860
  parsed,
1617
1861
  opts,
@@ -1619,13 +1863,10 @@ def annotate_stream(
1619
1863
  lookup_cache,
1620
1864
  stats,
1621
1865
  identity_key=identity.get(index),
1622
- # A pin forces one family; auto mode splits the batch per line,
1623
- # the assembler verdict first and the register vote as fallback.
1624
- line_isa=opts.isa
1625
- if not auto_isa
1626
- else line_family.get(index) or _line_isa(f"{parsed.mnemonic} {parsed.operands}"),
1866
+ line_isa=line_isa,
1627
1867
  line_number=index + 1,
1628
1868
  identity_cache=identity_cache,
1869
+ foreign=index in foreign if line_isa is None else False,
1629
1870
  )
1630
1871
  if collecting_json:
1631
1872
  if record is not None:
@@ -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