simdref 0.0.19__tar.gz → 0.0.21__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 (212) hide show
  1. {simdref-0.0.19/src/simdref.egg-info → simdref-0.0.21}/PKG-INFO +1 -1
  2. {simdref-0.0.19 → simdref-0.0.21}/pyproject.toml +1 -1
  3. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/cli.py +52 -16
  4. simdref-0.0.21/src/simdref/cuda.py +385 -0
  5. simdref-0.0.21/src/simdref/cuda_docs.py +1336 -0
  6. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/display.py +293 -60
  7. simdref-0.0.21/src/simdref/docblocks.py +803 -0
  8. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/export.py +56 -28
  9. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/filters.py +190 -4
  10. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/ingest_catalog.py +105 -3
  11. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/ingest_sources.py +32 -0
  12. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/models.py +4 -0
  13. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/ptx.py +327 -19
  14. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/tui.py +595 -52
  15. simdref-0.0.21/src/simdref/urls.py +46 -0
  16. {simdref-0.0.19 → simdref-0.0.21/src/simdref.egg-info}/PKG-INFO +1 -1
  17. {simdref-0.0.19 → simdref-0.0.21}/src/simdref.egg-info/SOURCES.txt +30 -0
  18. simdref-0.0.21/tests/test_annotate_microarch.py +55 -0
  19. simdref-0.0.21/tests/test_arm_arch_filter.py +159 -0
  20. simdref-0.0.21/tests/test_category_filter.py +84 -0
  21. simdref-0.0.21/tests/test_cli_perf_line.py +37 -0
  22. {simdref-0.0.19 → simdref-0.0.21}/tests/test_cuda_declaration_parser.py +40 -2
  23. simdref-0.0.21/tests/test_cuda_doc_blocks.py +121 -0
  24. simdref-0.0.21/tests/test_cuda_docs_guide.py +162 -0
  25. simdref-0.0.21/tests/test_cuda_docs_math.py +462 -0
  26. simdref-0.0.21/tests/test_cuda_floors.py +145 -0
  27. simdref-0.0.21/tests/test_cuda_join.py +217 -0
  28. simdref-0.0.21/tests/test_cuda_links.py +75 -0
  29. simdref-0.0.21/tests/test_cuda_tex.py +72 -0
  30. {simdref-0.0.19 → simdref-0.0.21}/tests/test_cuda_wiring.py +24 -0
  31. simdref-0.0.21/tests/test_detail_metadata.py +91 -0
  32. simdref-0.0.21/tests/test_doc_entry_merge.py +101 -0
  33. simdref-0.0.21/tests/test_empty_query_state.py +69 -0
  34. {simdref-0.0.19 → simdref-0.0.21}/tests/test_export.py +21 -20
  35. simdref-0.0.21/tests/test_export_site_data.py +109 -0
  36. simdref-0.0.21/tests/test_export_sm.py +72 -0
  37. simdref-0.0.21/tests/test_export_sm_column.py +86 -0
  38. {simdref-0.0.19 → simdref-0.0.21}/tests/test_filters.py +21 -3
  39. {simdref-0.0.19 → simdref-0.0.21}/tests/test_lsp_web.py +13 -7
  40. simdref-0.0.21/tests/test_perf_kind_toggle.py +84 -0
  41. {simdref-0.0.19 → simdref-0.0.21}/tests/test_presentation.py +10 -0
  42. simdref-0.0.21/tests/test_preset_chip_sync.py +45 -0
  43. simdref-0.0.21/tests/test_ptx.py +463 -0
  44. simdref-0.0.21/tests/test_related_entries.py +121 -0
  45. simdref-0.0.21/tests/test_result_row_badges.py +71 -0
  46. simdref-0.0.21/tests/test_section_order.py +97 -0
  47. simdref-0.0.21/tests/test_sm_filter.py +297 -0
  48. simdref-0.0.21/tests/test_tui.py +425 -0
  49. simdref-0.0.21/tests/test_tui_doc_blocks.py +424 -0
  50. simdref-0.0.21/tests/test_tui_doc_fixes.py +182 -0
  51. simdref-0.0.21/tests/test_tui_doc_fmt.py +373 -0
  52. simdref-0.0.21/tests/test_urls.py +71 -0
  53. simdref-0.0.19/src/simdref/cuda.py +0 -50
  54. simdref-0.0.19/tests/test_ptx.py +0 -253
  55. simdref-0.0.19/tests/test_tui.py +0 -244
  56. {simdref-0.0.19 → simdref-0.0.21}/LICENSE +0 -0
  57. {simdref-0.0.19 → simdref-0.0.21}/README.md +0 -0
  58. {simdref-0.0.19 → simdref-0.0.21}/setup.cfg +0 -0
  59. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/__init__.py +0 -0
  60. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/__main__.py +0 -0
  61. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/annotate.py +0 -0
  62. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/arm_instructions.py +0 -0
  63. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/asm_identity.py +0 -0
  64. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/asmline.py +0 -0
  65. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/catkey.py +0 -0
  66. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/htmltext.py +0 -0
  67. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/ingest.py +0 -0
  68. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/ingest_pdf.py +0 -0
  69. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/lsp.py +0 -0
  70. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/manpages.py +0 -0
  71. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/operands.py +0 -0
  72. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/pdfparse/__init__.py +0 -0
  73. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/pdfparse/base.py +0 -0
  74. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/pdfparse/intel.py +0 -0
  75. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/pdfparse/registry.py +0 -0
  76. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/pdfparse/types.py +0 -0
  77. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/pdfrefs.py +0 -0
  78. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/perf.py +0 -0
  79. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/perf_sources/__init__.py +0 -0
  80. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/perf_sources/amd_swopt.py +0 -0
  81. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/perf_sources/arm_swopt.py +0 -0
  82. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/perf_sources/cores.py +0 -0
  83. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/perf_sources/instlatx64.py +0 -0
  84. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/perf_sources/intel_guide.py +0 -0
  85. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/perf_sources/intel_optref.py +0 -0
  86. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/perf_sources/llvm_mca.py +0 -0
  87. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
  88. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/perf_sources/merge.py +0 -0
  89. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/queries.py +0 -0
  90. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/regtable.py +0 -0
  91. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/riscv.py +0 -0
  92. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/search.py +0 -0
  93. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/storage.py +0 -0
  94. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/tablecols.py +0 -0
  95. {simdref-0.0.19 → simdref-0.0.21}/src/simdref/ui_labels.py +0 -0
  96. {simdref-0.0.19 → simdref-0.0.21}/src/simdref.egg-info/dependency_links.txt +0 -0
  97. {simdref-0.0.19 → simdref-0.0.21}/src/simdref.egg-info/entry_points.txt +0 -0
  98. {simdref-0.0.19 → simdref-0.0.21}/src/simdref.egg-info/requires.txt +0 -0
  99. {simdref-0.0.19 → simdref-0.0.21}/src/simdref.egg-info/top_level.txt +0 -0
  100. {simdref-0.0.19 → simdref-0.0.21}/tests/test_a1_sdm_parser.py +0 -0
  101. {simdref-0.0.19 → simdref-0.0.21}/tests/test_a2_perf_rule.py +0 -0
  102. {simdref-0.0.19 → simdref-0.0.21}/tests/test_a4_arm_pdfrefs.py +0 -0
  103. {simdref-0.0.19 → simdref-0.0.21}/tests/test_a5_offline_aarchmrs.py +0 -0
  104. {simdref-0.0.19 → simdref-0.0.21}/tests/test_amd_match_llvm_mc.py +0 -0
  105. {simdref-0.0.19 → simdref-0.0.21}/tests/test_annotate.py +0 -0
  106. {simdref-0.0.19 → simdref-0.0.21}/tests/test_annotate_positions.py +0 -0
  107. {simdref-0.0.19 → simdref-0.0.21}/tests/test_annotate_scans.py +0 -0
  108. {simdref-0.0.19 → simdref-0.0.21}/tests/test_arrangement_tail.py +0 -0
  109. {simdref-0.0.19 → simdref-0.0.21}/tests/test_asm_identity.py +0 -0
  110. {simdref-0.0.19 → simdref-0.0.21}/tests/test_asm_wire.py +0 -0
  111. {simdref-0.0.19 → simdref-0.0.21}/tests/test_asmline.py +0 -0
  112. {simdref-0.0.19 → simdref-0.0.21}/tests/test_audit_coverage.py +0 -0
  113. {simdref-0.0.19 → simdref-0.0.21}/tests/test_auto_update_on_version_change.py +0 -0
  114. {simdref-0.0.19 → simdref-0.0.21}/tests/test_b1_latency_pairs.py +0 -0
  115. {simdref-0.0.19 → simdref-0.0.21}/tests/test_b2_metadata.py +0 -0
  116. {simdref-0.0.19 → simdref-0.0.21}/tests/test_b3_sdm_forms.py +0 -0
  117. {simdref-0.0.19 → simdref-0.0.21}/tests/test_b4_descriptions_provenance.py +0 -0
  118. {simdref-0.0.19 → simdref-0.0.21}/tests/test_b5_linking.py +0 -0
  119. {simdref-0.0.19 → simdref-0.0.21}/tests/test_b5_perf_family.py +0 -0
  120. {simdref-0.0.19 → simdref-0.0.21}/tests/test_b5_sme.py +0 -0
  121. {simdref-0.0.19 → simdref-0.0.21}/tests/test_c0_perf_priority.py +0 -0
  122. {simdref-0.0.19 → simdref-0.0.21}/tests/test_catkey.py +0 -0
  123. {simdref-0.0.19 → simdref-0.0.21}/tests/test_cli_bare_query.py +0 -0
  124. {simdref-0.0.19 → simdref-0.0.21}/tests/test_cli_bootstrap_progress.py +0 -0
  125. {simdref-0.0.19 → simdref-0.0.21}/tests/test_cli_help.py +0 -0
  126. {simdref-0.0.19 → simdref-0.0.21}/tests/test_cli_llm.py +0 -0
  127. {simdref-0.0.19 → simdref-0.0.21}/tests/test_coverage_live.py +0 -0
  128. {simdref-0.0.19 → simdref-0.0.21}/tests/test_coverage_parity.py +0 -0
  129. {simdref-0.0.19 → simdref-0.0.21}/tests/test_cuda_intrinsics.py +0 -0
  130. {simdref-0.0.19 → simdref-0.0.21}/tests/test_cuda_td_expansion.py +0 -0
  131. {simdref-0.0.19 → simdref-0.0.21}/tests/test_display.py +0 -0
  132. {simdref-0.0.19 → simdref-0.0.21}/tests/test_display_scanners.py +0 -0
  133. {simdref-0.0.19 → simdref-0.0.21}/tests/test_export_viz.py +0 -0
  134. {simdref-0.0.19 → simdref-0.0.21}/tests/test_fetch_retry.py +0 -0
  135. {simdref-0.0.19 → simdref-0.0.21}/tests/test_fixui_display.py +0 -0
  136. {simdref-0.0.19 → simdref-0.0.21}/tests/test_htmltext.py +0 -0
  137. {simdref-0.0.19 → simdref-0.0.21}/tests/test_ingest_enc_fields.py +0 -0
  138. {simdref-0.0.19 → simdref-0.0.21}/tests/test_ingest_sources.py +0 -0
  139. {simdref-0.0.19 → simdref-0.0.21}/tests/test_int_arm_isa_label.py +0 -0
  140. {simdref-0.0.19 → simdref-0.0.21}/tests/test_int_core_ids.py +0 -0
  141. {simdref-0.0.19 → simdref-0.0.21}/tests/test_int_ingest_registration.py +0 -0
  142. {simdref-0.0.19 → simdref-0.0.21}/tests/test_int_sdm_family_fallback.py +0 -0
  143. {simdref-0.0.19 → simdref-0.0.21}/tests/test_intel_operation_and_url.py +0 -0
  144. {simdref-0.0.19 → simdref-0.0.21}/tests/test_isa_detect_llvm_mc.py +0 -0
  145. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue2_fixes.py +0 -0
  146. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_51.py +0 -0
  147. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_52.py +0 -0
  148. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_53_54.py +0 -0
  149. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_55.py +0 -0
  150. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_56_57a.py +0 -0
  151. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_57_cpi.py +0 -0
  152. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_60_c1.py +0 -0
  153. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_60_contract.py +0 -0
  154. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_60_crossarch.py +0 -0
  155. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_60_matcher.py +0 -0
  156. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_65_arm_alias.py +0 -0
  157. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_65_branch_target.py +0 -0
  158. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_65_compressed.py +0 -0
  159. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_65_foreign_arch.py +0 -0
  160. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_65_identity.py +0 -0
  161. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_65_m1.py +0 -0
  162. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_65_m5.py +0 -0
  163. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_65_perfdata.py +0 -0
  164. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_65_sme_za.py +0 -0
  165. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_65_text.py +0 -0
  166. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issue_65_timing.py +0 -0
  167. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issues_23_24_25.py +0 -0
  168. {simdref-0.0.19 → simdref-0.0.21}/tests/test_issues_51_57_join.py +0 -0
  169. {simdref-0.0.19 → simdref-0.0.21}/tests/test_leftover_arm_form_pick.py +0 -0
  170. {simdref-0.0.19 → simdref-0.0.21}/tests/test_leftover_arm_postindex.py +0 -0
  171. {simdref-0.0.19 → simdref-0.0.21}/tests/test_leftover_att_movx.py +0 -0
  172. {simdref-0.0.19 → simdref-0.0.21}/tests/test_leftover_autoupdate.py +0 -0
  173. {simdref-0.0.19 → simdref-0.0.21}/tests/test_leftover_catalog_gaps.py +0 -0
  174. {simdref-0.0.19 → simdref-0.0.21}/tests/test_leftover_ingest_isa_merge.py +0 -0
  175. {simdref-0.0.19 → simdref-0.0.21}/tests/test_leftover_intel_size.py +0 -0
  176. {simdref-0.0.19 → simdref-0.0.21}/tests/test_leftover_perf_join.py +0 -0
  177. {simdref-0.0.19 → simdref-0.0.21}/tests/test_leftover_riscv_shared.py +0 -0
  178. {simdref-0.0.19 → simdref-0.0.21}/tests/test_lsp_cuda.py +0 -0
  179. {simdref-0.0.19 → simdref-0.0.21}/tests/test_lsp_ptx.py +0 -0
  180. {simdref-0.0.19 → simdref-0.0.21}/tests/test_lsp_stdio.py +0 -0
  181. {simdref-0.0.19 → simdref-0.0.21}/tests/test_models.py +0 -0
  182. {simdref-0.0.19 → simdref-0.0.21}/tests/test_no_catalog_row.py +0 -0
  183. {simdref-0.0.19 → simdref-0.0.21}/tests/test_objdump_listings.py +0 -0
  184. {simdref-0.0.19 → simdref-0.0.21}/tests/test_operands.py +0 -0
  185. {simdref-0.0.19 → simdref-0.0.21}/tests/test_pdfparse.py +0 -0
  186. {simdref-0.0.19 → simdref-0.0.21}/tests/test_perf.py +0 -0
  187. {simdref-0.0.19 → simdref-0.0.21}/tests/test_perf_source_amd_swopt.py +0 -0
  188. {simdref-0.0.19 → simdref-0.0.21}/tests/test_perf_source_arm_swopt.py +0 -0
  189. {simdref-0.0.19 → simdref-0.0.21}/tests/test_perf_source_instlatx64.py +0 -0
  190. {simdref-0.0.19 → simdref-0.0.21}/tests/test_perf_source_intel_guide.py +0 -0
  191. {simdref-0.0.19 → simdref-0.0.21}/tests/test_perf_source_intel_optref.py +0 -0
  192. {simdref-0.0.19 → simdref-0.0.21}/tests/test_perf_sources.py +0 -0
  193. {simdref-0.0.19 → simdref-0.0.21}/tests/test_preset_persistence.py +0 -0
  194. {simdref-0.0.19 → simdref-0.0.21}/tests/test_regtable.py +0 -0
  195. {simdref-0.0.19 → simdref-0.0.21}/tests/test_release_tag_pin.py +0 -0
  196. {simdref-0.0.19 → simdref-0.0.21}/tests/test_riscv_docs_pages.py +0 -0
  197. {simdref-0.0.19 → simdref-0.0.21}/tests/test_search.py +0 -0
  198. {simdref-0.0.19 → simdref-0.0.21}/tests/test_search_pushdown.py +0 -0
  199. {simdref-0.0.19 → simdref-0.0.21}/tests/test_shared_source_helpers.py +0 -0
  200. {simdref-0.0.19 → simdref-0.0.21}/tests/test_source_kind_filter.py +0 -0
  201. {simdref-0.0.19 → simdref-0.0.21}/tests/test_source_validation.py +0 -0
  202. {simdref-0.0.19 → simdref-0.0.21}/tests/test_storage_alnum.py +0 -0
  203. {simdref-0.0.19 → simdref-0.0.21}/tests/test_storage_payload.py +0 -0
  204. {simdref-0.0.19 → simdref-0.0.21}/tests/test_storage_schema.py +0 -0
  205. {simdref-0.0.19 → simdref-0.0.21}/tests/test_tablecols.py +0 -0
  206. {simdref-0.0.19 → simdref-0.0.21}/tests/test_ui_labels_parity.py +0 -0
  207. {simdref-0.0.19 → simdref-0.0.21}/tests/test_update_build_stamp.py +0 -0
  208. {simdref-0.0.19 → simdref-0.0.21}/tests/test_vendor_riscv_categories.py +0 -0
  209. {simdref-0.0.19 → simdref-0.0.21}/tests/test_vendor_riscv_sources.py +0 -0
  210. {simdref-0.0.19 → simdref-0.0.21}/tests/test_version_matches_pyproject.py +0 -0
  211. {simdref-0.0.19 → simdref-0.0.21}/tests/test_x86_key_widths.py +0 -0
  212. {simdref-0.0.19 → simdref-0.0.21}/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.19
3
+ Version: 0.0.21
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simdref"
7
- version = "0.0.19"
7
+ version = "0.0.21"
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"
@@ -917,11 +917,13 @@ def _payload_source_kinds(arch_details) -> list[str]:
917
917
  # ---------------------------------------------------------------------------
918
918
 
919
919
 
920
- def _print_search_results_runtime(conn, query: str, limit: int = 20, as_json: bool = False) -> int:
920
+ def _print_search_results_runtime(
921
+ conn, query: str, limit: int = 20, as_json: bool = False, state=None
922
+ ) -> int:
921
923
  """Render the ranked search table (or JSON) in the search's own
922
924
  relevance order. Returns the number of rows actually printed — callers
923
925
  use 0 to fall through to the no-match branch instead of printing an
924
- empty table."""
926
+ empty table. *state* hides rows the sm filter excludes."""
925
927
  results, intrinsic_map, instruction_map = _search_runtime(conn, query, limit=limit)
926
928
  prepared_rows = []
927
929
  for result in results:
@@ -934,6 +936,8 @@ def _print_search_results_runtime(conn, query: str, limit: int = 20, as_json: bo
934
936
  if item is not None:
935
937
  if not isa_visible(item.isa, show_fp16=SHOW_FP16_ISAS):
936
938
  continue
939
+ if state is not None and not state.matches(item):
940
+ continue
937
941
  arch = display_architecture(item.architecture)
938
942
  isa = display_isa(item.isa)
939
943
  lat, cpi = variant_perf_summary(item.arch_details, perf.record_iclass(item))
@@ -942,6 +946,8 @@ def _print_search_results_runtime(conn, query: str, limit: int = 20, as_json: bo
942
946
  if item is not None:
943
947
  if not isa_visible(item.isa, show_fp16=SHOW_FP16_ISAS):
944
948
  continue
949
+ if state is not None and not state.matches(item):
950
+ continue
945
951
  arch = display_architecture(item.architecture)
946
952
  isa = display_isa(item.isa)
947
953
  lat, cpi = intrinsic_perf_summary_runtime(conn, item, instruction_map)
@@ -987,18 +993,31 @@ def _smart_lookup(
987
993
  preset: str | None = None,
988
994
  arch: str | None = None,
989
995
  as_json: bool = False,
996
+ sm: str | None = None,
990
997
  ) -> int:
991
998
  """Dispatch a bare query.
992
999
 
993
1000
  Exact intrinsic match prints the intrinsic detail and exits 0. Exact
994
1001
  instruction match prints a plain-text (or JSON) summary and exits 0;
995
1002
  ``--arch`` applies to this branch only. Fuzzy hits print the ranked
996
- search list (or JSON) and exit 0, on a TTY or not. Nothing found opens
1003
+ search list (or JSON) and exit 0, on a TTY or not. ``--sm`` hides the
1004
+ records whose sm floor fails the chosen level. Nothing found opens
997
1005
  the TUI on a TTY (never under ``--json``) or exits 2 non-interactively.
998
1006
  """
999
1007
  ensure_runtime()
1008
+ from simdref.filters import FilterState, sm_number
1009
+
1010
+ if sm:
1011
+ try:
1012
+ sm_number(sm)
1013
+ except ValueError:
1014
+ err_console.print(f"--sm {sm!r} is not an sm level (want sm_XX)", style="red")
1015
+ return 1
1016
+ state = FilterState(min_sm=sm or "")
1000
1017
  with contextlib.closing(open_db()) as conn:
1001
1018
  intrinsic = load_intrinsic_from_db(conn, query)
1019
+ if intrinsic is not None and not state.matches(intrinsic):
1020
+ intrinsic = None
1002
1021
  if intrinsic is not None and arch is not None:
1003
1022
  err_console.print("--arch applies to instruction queries only", style="red")
1004
1023
  return 2
@@ -1008,7 +1027,7 @@ def _smart_lookup(
1008
1027
  else:
1009
1028
  render_intrinsic(None, intrinsic, conn=conn)
1010
1029
  return 0
1011
- records = _find_instructions_fast(query)
1030
+ records = [r for r in _find_instructions_fast(query) if state.matches(r)]
1012
1031
  if records:
1013
1032
  return _print_non_interactive_summary(
1014
1033
  query, records=records, arch=arch, as_json=as_json
@@ -1016,7 +1035,7 @@ def _smart_lookup(
1016
1035
  if arch is not None:
1017
1036
  err_console.print("--arch applies to instruction queries only", style="red")
1018
1037
  return 2
1019
- printed = _print_search_results_runtime(conn, query, as_json=as_json)
1038
+ printed = _print_search_results_runtime(conn, query, as_json=as_json, state=state)
1020
1039
  if printed:
1021
1040
  if not as_json and sys.stdin.isatty() and sys.stdout.isatty():
1022
1041
  # A fuzzy match on a TTY opens the TUI (preset as passed, like
@@ -1158,14 +1177,18 @@ def _print_non_interactive_summary(
1158
1177
  typer.echo(f" {tag}: lat={lat_text}c cpi={_fmt_num(cpi)} ports={ports_str}")
1159
1178
  else:
1160
1179
  summary = aggregate_perf(rec, mode="avg", include_modeled=False)
1161
- ports = collect_ports(rec, arch=None, archs_used=summary.archs_used)
1162
- ports_str = ",".join(ports) if ports else "-"
1163
- typer.echo(
1164
- f" avg over {summary.n_archs} arch(s) [{summary.source_kind}]: "
1165
- f"lat={_fmt_num(summary.latency)}c cpi={_fmt_num(summary.cpi)} ports={ports_str}"
1166
- )
1167
- if summary.archs_used:
1168
- typer.echo(f" archs: {', '.join(summary.archs_used)}")
1180
+ if summary.n_archs == 0:
1181
+ # The TUI wording (tui._NO_DATA): no arch holds perf data.
1182
+ typer.echo(" performance: no data")
1183
+ else:
1184
+ ports = collect_ports(rec, arch=None, archs_used=summary.archs_used)
1185
+ ports_str = ",".join(ports) if ports else "-"
1186
+ typer.echo(
1187
+ f" avg over {summary.n_archs} arch(s) [{summary.source_kind}]: "
1188
+ f"lat={_fmt_num(summary.latency)}c cpi={_fmt_num(summary.cpi)} ports={ports_str}"
1189
+ )
1190
+ if summary.archs_used:
1191
+ typer.echo(f" archs: {', '.join(summary.archs_used)}")
1169
1192
  return 0
1170
1193
 
1171
1194
 
@@ -1992,7 +2015,7 @@ def llm_query(
1992
2015
  preset: str = typer.Option(
1993
2016
  None,
1994
2017
  "--preset",
1995
- help="Apply a named preset (default, intel, arm32, arm64, riscv, none, all).",
2018
+ help="Apply a named preset (default, intel, arm32, arm64, riscv, cuda, none, all).",
1996
2019
  ),
1997
2020
  source_kind: str = typer.Option(
1998
2021
  "any", "--source-kind", help="Filter perf rows by provenance: measured, modeled, or any."
@@ -2120,7 +2143,7 @@ def llm_batch(
2120
2143
  preset: str = typer.Option(
2121
2144
  None,
2122
2145
  "--preset",
2123
- help="Apply a named preset (default, intel, arm32, arm64, riscv, none, all).",
2146
+ help="Apply a named preset (default, intel, arm32, arm64, riscv, cuda, none, all).",
2124
2147
  ),
2125
2148
  source_kind: str = typer.Option(
2126
2149
  "any", "--source-kind", help="Filter perf rows by provenance: measured, modeled, or any."
@@ -2471,6 +2494,7 @@ def main() -> int:
2471
2494
  # dispatch.
2472
2495
  initial_preset: str | None = None
2473
2496
  bare_arch: str | None = None
2497
+ bare_sm: str | None = None
2474
2498
  bare_json = False
2475
2499
  _cleaned: list[str] = []
2476
2500
  _i = 0
@@ -2492,6 +2516,14 @@ def main() -> int:
2492
2516
  bare_arch = arg.split("=", 1)[1]
2493
2517
  _i += 1
2494
2518
  continue
2519
+ if arg == "--sm" and _i + 1 < len(argv):
2520
+ bare_sm = argv[_i + 1]
2521
+ _i += 2
2522
+ continue
2523
+ if arg.startswith("--sm="):
2524
+ bare_sm = arg.split("=", 1)[1]
2525
+ _i += 1
2526
+ continue
2495
2527
  if arg == "--json":
2496
2528
  bare_json = True
2497
2529
  _i += 1
@@ -2531,7 +2563,11 @@ def main() -> int:
2531
2563
  if len(argv) > 1 and argv[0].lower() in {"show", "lookup", "info"}:
2532
2564
  argv = argv[1:]
2533
2565
  return _smart_lookup(
2534
- " ".join(argv), preset=initial_preset, arch=bare_arch, as_json=bare_json
2566
+ " ".join(argv),
2567
+ preset=initial_preset,
2568
+ arch=bare_arch,
2569
+ as_json=bare_json,
2570
+ sm=bare_sm,
2535
2571
  )
2536
2572
  if not argv:
2537
2573
  ensure_runtime()
@@ -0,0 +1,385 @@
1
+ """CUDA C++ device intrinsics parsing and derived record rows.
2
+
3
+ Parses the JSON bundle produced by tools/vendor_cuda_sources.py, which
4
+ extracts CUDA API names from four clang headers and resolves PTX
5
+ instruction links by parsing LLVM TableGen .td files. Joins the NVIDIA
6
+ documentation pages, parsed by simdref.cuda_docs, into the rows. The
7
+ join runs at build time. No NVIDIA text is stored in git.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ from typing import Any
14
+
15
+ from simdref.cuda_docs import declares_name, tex_to_text
16
+ from simdref.docblocks import merge_doc_entries
17
+ from simdref.models import IntrinsicRecord
18
+ from simdref.urls import resolve_url
19
+
20
+ BUNDLE_FORMAT = "clang-cuda-headers-v2"
21
+ CUDA_INTRINSICS_DOC_URL = "https://docs.nvidia.com/cuda/cuda-c-programming-guide/"
22
+
23
+
24
+ def parse_cuda_intrinsics_payload(text: str) -> list[IntrinsicRecord]:
25
+ """Parse the CUDA intrinsics JSON bundle into IntrinsicRecord rows."""
26
+ data = json.loads(text)
27
+ if data.get("format") != BUNDLE_FORMAT:
28
+ raise ValueError(f"Unexpected bundle format: {data.get('format')!r}")
29
+
30
+ rows: list[IntrinsicRecord] = []
31
+ for item in data.get("intrinsics", []):
32
+ metadata = item.get("metadata", {})
33
+ classification = metadata.get("classification", "")
34
+ record_metadata = {"classification": classification}
35
+ for key in ("min_sm", "min_ptx", "min_sm_from", "min_ptx_from"):
36
+ value = str(item.get(key) or metadata.get(key) or "").strip()
37
+ if value:
38
+ record_metadata[key] = value
39
+
40
+ row = IntrinsicRecord(
41
+ name=item["name"],
42
+ signature=item.get("signature", ""),
43
+ description=item.get("description", ""),
44
+ header=item.get("header", ""),
45
+ url=item.get("url", ""),
46
+ architecture="cuda",
47
+ isa=["CUDA"],
48
+ category=item.get("category", ""),
49
+ subcategory=item.get("subcategory", ""),
50
+ instructions=item.get("instructions", []),
51
+ instruction_refs=item.get("instruction_refs", []),
52
+ metadata=record_metadata,
53
+ doc_sections=item.get("doc_sections", {}),
54
+ notes=item.get("notes", []),
55
+ aliases=item.get("aliases", []),
56
+ source="clang-cuda-headers",
57
+ )
58
+ rows.append(row)
59
+
60
+ return rows
61
+
62
+
63
+ def parse_cuda_calls(text: str) -> dict[str, list[str]]:
64
+ """name to call-name list from the bundle call graph.
65
+
66
+ The vendor step records, per header function, the names its body
67
+ calls. This is the graph the doc join and the SM floors walk.
68
+ """
69
+ data = json.loads(text)
70
+ if data.get("format") != BUNDLE_FORMAT:
71
+ raise ValueError(f"Unexpected bundle format: {data.get('format')!r}")
72
+ out: dict[str, list[str]] = {}
73
+ for item in data.get("intrinsics", []):
74
+ out[item["name"]] = [str(name) for name in item.get("calls") or []]
75
+ return out
76
+
77
+
78
+ def _signature_params(signature: str) -> str:
79
+ """The argument list of a signature, its parameter names and types.
80
+
81
+ The math pages carry no Parameters field. The signature does. This
82
+ takes the text between the first "(" and the last ")" of it.
83
+ """
84
+ start = signature.find("(")
85
+ end = signature.rfind(")")
86
+ if start == -1 or end <= start:
87
+ return ""
88
+ args = " ".join(signature[start + 1 : end].split())
89
+ if not args or args == "void":
90
+ return ""
91
+ return args
92
+
93
+
94
+ def _first_sentence(text: str) -> str:
95
+ """The first sentence of a description, with its full stop."""
96
+ joined = " ".join(text.split())
97
+ if not joined:
98
+ return ""
99
+ stop = joined.find(". ")
100
+ if stop == -1:
101
+ return joined
102
+ return joined[: stop + 1]
103
+
104
+
105
+ def _stripped(name: str) -> str:
106
+ """A name as one lowercase run of characters, for containment."""
107
+ return name.replace("_", "").lower()
108
+
109
+
110
+ def _reachable_builtins(name: str, calls: dict[str, list[str]]) -> set[str]:
111
+ """The __nvvm_ and __nv_ tokens the name reaches over the call graph."""
112
+ seen = {name}
113
+ stack = [name]
114
+ out: set[str] = set()
115
+ while stack:
116
+ current = stack.pop()
117
+ for token in calls.get(current, []):
118
+ if token.startswith("__nvvm_") or token.startswith("__nv_"):
119
+ out.add(token)
120
+ elif token in calls and token not in seen:
121
+ seen.add(token)
122
+ stack.append(token)
123
+ return out
124
+
125
+
126
+ def _nearest_documented_caller(name: str, calls: dict[str, list[str]], documented: set[str]) -> str:
127
+ """The nearest documented name that calls this one, else "".
128
+
129
+ Breadth first over the caller side of the call graph. The nearest
130
+ level with a documented name decides. Ties take the shortest name.
131
+ """
132
+ callers: dict[str, list[str]] = {}
133
+ for caller, callees in calls.items():
134
+ for callee in callees:
135
+ callers.setdefault(callee, []).append(caller)
136
+ seen = {name}
137
+ frontier = [name]
138
+ while frontier:
139
+ next_frontier: list[str] = []
140
+ candidates: list[str] = []
141
+ for current in frontier:
142
+ for caller in callers.get(current, []):
143
+ if caller in documented and caller not in candidates:
144
+ candidates.append(caller)
145
+ if caller not in seen:
146
+ seen.add(caller)
147
+ next_frontier.append(caller)
148
+ if candidates:
149
+ return min(candidates, key=lambda item: (len(item), item))
150
+ frontier = next_frontier
151
+ return ""
152
+
153
+
154
+ def _contained_doc_name(
155
+ name: str, calls: dict[str, list[str]], documented: dict[str, tuple[dict, str]]
156
+ ) -> str:
157
+ """A documented name that this name reaches by name containment.
158
+
159
+ Last join tier. The documented name, without underscores, must sit
160
+ inside the row name or inside one of the builtins the row reaches,
161
+ from a word start of that name. The row name is tried first, so a
162
+ helper such as __iAtomicExch maps to atomicExch and not to the type
163
+ cast its body calls. The longest such name wins. This maps the
164
+ header helpers onto the API names the docs use.
165
+ """
166
+ for needles in (
167
+ [name],
168
+ sorted(_reachable_builtins(name, calls)),
169
+ ):
170
+ best = ""
171
+ best_len = 0
172
+ for doc_name, (doc_entry, _source) in sorted(documented.items()):
173
+ # A tier-3 name must be a documented function, not a word of
174
+ # an example or a feature table cell.
175
+ signature = str(doc_entry.get("signature") or "")
176
+ if not declares_name(signature, doc_name):
177
+ continue
178
+ token = _stripped(doc_name)
179
+ if len(token) < 4:
180
+ continue
181
+ if any(_starts_at_word(token, needle) for needle in needles) and len(token) > best_len:
182
+ best = doc_name
183
+ best_len = len(token)
184
+ if best:
185
+ return best
186
+ return ""
187
+
188
+
189
+ def _starts_at_word(token: str, name: str) -> bool:
190
+ """The token matches the name from a word start, underscores aside.
191
+
192
+ Word starts are the name start, the letter after an underscore or
193
+ a digit, and a case hump. Matching skips underscores of the name,
194
+ so a multiword builtin name still matches its word run.
195
+ """
196
+ n = len(name)
197
+ for pos in range(n):
198
+ if pos:
199
+ prev = name[pos - 1]
200
+ hump = name[pos].isupper() and not prev.isupper()
201
+ if prev != "_" and not prev.isdigit() and not hump:
202
+ continue
203
+ i = pos
204
+ ok = True
205
+ for ch in token:
206
+ while i < n and name[i] == "_":
207
+ i += 1
208
+ # A width run in the name, such as the 32 of match32, does
209
+ # not split the word for the doc name.
210
+ if i < n and name[i].isdigit() and name[i].lower() != ch:
211
+ while i < n and (name[i].isdigit() or name[i] == "_"):
212
+ i += 1
213
+ if i >= n or name[i].lower() != ch:
214
+ ok = False
215
+ break
216
+ i += 1
217
+ if ok:
218
+ return True
219
+ return False
220
+
221
+
222
+ def _first_paragraph_text(entry: dict[str, Any]) -> str:
223
+ """Text of the first paragraph block of the entry, else "".
224
+
225
+ The summary is a paragraph, never list text: the flat description
226
+ string of a guide entry merges a lead-in paragraph with the list
227
+ items that follow it (the atomicAdd steps).
228
+ """
229
+ for section in entry.get("sections") or []:
230
+ for block in section.get("blocks") or []:
231
+ if block.get("kind") == "paragraph" and str(block.get("text") or "").strip():
232
+ return str(block["text"])
233
+ return ""
234
+
235
+
236
+ def apply_cuda_docs(
237
+ rows: list[IntrinsicRecord], calls: dict[str, list[str]], docs: dict[str, Any]
238
+ ) -> None:
239
+ """Fill documentation fields of the CUDA rows from the parsed pages.
240
+
241
+ Join tiers per name: an exact doc entry, then the nearest documented
242
+ caller over the header call graph, then a documented name contained
243
+ in the row name or in a builtin it reaches. The chosen entry stores
244
+ its caller in metadata["doc_via"] on the fallback tiers. A name with
245
+ no entry keeps no URL and keeps the header signature.
246
+ """
247
+ ulp = docs.get("ulp") or {}
248
+ documented: dict[str, tuple[dict, str]] = {}
249
+ for mapping, source in (
250
+ (docs.get("math") or {}, "cuda-math-api"),
251
+ (docs.get("guide") or {}, "cuda-guide"),
252
+ (docs.get("guide_archive") or {}, "cuda-guide-archive"),
253
+ (docs.get("libdevice") or {}, "libdevice"),
254
+ ):
255
+ for doc_name, entry in mapping.items():
256
+ if doc_name not in documented:
257
+ documented[doc_name] = (entry, source)
258
+ for row in rows:
259
+ name = row.name
260
+ via = ""
261
+ hit = documented.get(name)
262
+ if hit is None:
263
+ via = _nearest_documented_caller(name, calls, set(documented))
264
+ if via:
265
+ hit = documented[via]
266
+ if hit is None:
267
+ via = _contained_doc_name(name, calls, documented)
268
+ if via:
269
+ hit = documented[via]
270
+ if hit is None:
271
+ row.url = ""
272
+ continue
273
+ entry, source = hit
274
+ anchor = str(entry.get("anchor") or "")
275
+ page = str(entry.get("page") or "")
276
+ row.url = resolve_url(page, "#" + anchor) if anchor else resolve_url(page)
277
+ signature = str(entry.get("signature") or "")
278
+ # A row shows a declaration of its own name, never an example
279
+ # call or a sibling's declaration: the header signature stays
280
+ # when the doc entry declares another name.
281
+ if signature and declares_name(signature, name):
282
+ row.signature = signature
283
+ description = str(entry.get("description") or "")
284
+ summary = str(entry.get("summary") or "")
285
+ row.description = _first_sentence(summary or _first_paragraph_text(entry) or description)
286
+ sections: dict[str, str] = {}
287
+ if description:
288
+ sections["Description"] = description
289
+ parameters = str(entry.get("parameters") or "")
290
+ if not parameters:
291
+ parameters = _signature_params(str(entry.get("signature") or ""))
292
+ if parameters:
293
+ sections["Parameters"] = parameters
294
+ if entry.get("return_value"):
295
+ sections["Return"] = str(entry["return_value"])
296
+ ulp_entry = ulp.get(name) or {}
297
+ accuracy = str(ulp_entry.get("accuracy") or "")
298
+ if not accuracy:
299
+ accuracy = str(entry.get("accuracy") or "")
300
+ if accuracy:
301
+ readable = tex_to_text(accuracy)
302
+ sections["Accuracy"] = readable
303
+ # The web renders the original TeX later; keep it verbatim.
304
+ if readable != accuracy:
305
+ row.metadata["doc_math"] = accuracy
306
+ row.doc_sections = sections
307
+ row.metadata["doc_source"] = source
308
+ if via:
309
+ row.metadata["doc_via"] = via
310
+ row.doc_sources = _doc_sources(entry, source, row.url, via, page)
311
+ ulp_blocks = _ulp_doc_entry(ulp_entry)
312
+ if ulp_blocks is not None:
313
+ row.doc_sources.append(ulp_blocks)
314
+
315
+
316
+ def _doc_sources(
317
+ entry: dict[str, Any], source: str, url: str, via: str, page: str
318
+ ) -> list[dict[str, Any]]:
319
+ """The row's own C.1 entry for the page that documents it."""
320
+ sections = entry.get("sections") or []
321
+ block_entry: dict[str, Any] = {
322
+ "source": source,
323
+ "url": url,
324
+ "page": resolve_url(page),
325
+ "title": str(entry.get("title") or ""),
326
+ "heading": str(entry.get("heading") or ""),
327
+ "shared": bool(via) or any(bool(item.get("shared")) for item in sections),
328
+ "sections": sections,
329
+ }
330
+ if entry.get("signature"):
331
+ block_entry["signature"] = str(entry["signature"])
332
+ if entry.get("signature_runs"):
333
+ block_entry["signature_runs"] = entry["signature_runs"]
334
+ if via:
335
+ block_entry["via"] = via
336
+ return [block_entry]
337
+
338
+
339
+ def _ulp_doc_entry(ulp_entry: dict[str, Any]) -> dict[str, Any] | None:
340
+ """The ULP table of the name as one C.1 table entry, else None."""
341
+ rows = [list(cells) for cells in (ulp_entry.get("rows") or [])]
342
+ if not rows:
343
+ return None
344
+ caption = str(ulp_entry.get("caption") or "")
345
+ blocks: list[dict[str, Any]] = [
346
+ {
347
+ "kind": "table",
348
+ "caption": caption,
349
+ # The entry keeps the header as one flat list of cell
350
+ # strings; the table block carries rows of cells. Wrapping
351
+ # once keeps the cells as strings: iterating the flat list
352
+ # would split each cell into its letters.
353
+ "header": [list(ulp_entry.get("header") or [])],
354
+ "rows": rows,
355
+ }
356
+ ]
357
+ accuracy = str(ulp_entry.get("accuracy") or "")
358
+ if ulp_entry.get("math") and accuracy:
359
+ blocks.append({"kind": "math", "text": tex_to_text(accuracy), "tex": accuracy})
360
+ return {
361
+ "source": "ulp",
362
+ "url": str(ulp_entry.get("page") or ""),
363
+ "page": str(ulp_entry.get("page") or ""),
364
+ "title": str(ulp_entry.get("title") or ""),
365
+ "heading": caption,
366
+ "shared": False,
367
+ "sections": [{"heading": "", "shared": False, "blocks": blocks}],
368
+ }
369
+
370
+
371
+ def append_linked_ptx_docs(rows: list[IntrinsicRecord], instructions: list[Any]) -> None:
372
+ """Append the linked PTX entries to the CUDA rows (design C.3).
373
+
374
+ One entry per PTX page. Instruction order first, then document
375
+ order. A row with no linked instruction keeps no PTX entry.
376
+ """
377
+ by_name = {item.mnemonic: item for item in instructions if item.architecture == "ptx"}
378
+ for row in rows:
379
+ for ref in row.instruction_refs:
380
+ target = str(ref.get("name") or "") if isinstance(ref, dict) else str(ref or "")
381
+ linked = by_name.get(target)
382
+ if linked is None:
383
+ continue
384
+ for doc_entry in linked.doc_sources:
385
+ merge_doc_entries(row.doc_sources, doc_entry)