simdref 0.0.17__tar.gz → 0.0.19__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.17/src/simdref.egg-info → simdref-0.0.19}/PKG-INFO +3 -2
  2. {simdref-0.0.17 → simdref-0.0.19}/README.md +2 -1
  3. {simdref-0.0.17 → simdref-0.0.19}/pyproject.toml +1 -1
  4. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/annotate.py +138 -14
  5. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/asm_identity.py +35 -0
  6. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/asmline.py +132 -12
  7. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/catkey.py +36 -2
  8. simdref-0.0.19/src/simdref/cuda.py +50 -0
  9. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/display.py +13 -2
  10. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/filters.py +1 -0
  11. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/ingest_catalog.py +7 -0
  12. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/ingest_sources.py +22 -0
  13. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/lsp.py +150 -18
  14. {simdref-0.0.17 → simdref-0.0.19/src/simdref.egg-info}/PKG-INFO +3 -2
  15. {simdref-0.0.17 → simdref-0.0.19}/src/simdref.egg-info/SOURCES.txt +10 -0
  16. {simdref-0.0.17 → simdref-0.0.19}/tests/test_asm_wire.py +73 -4
  17. {simdref-0.0.17 → simdref-0.0.19}/tests/test_catkey.py +2 -0
  18. simdref-0.0.19/tests/test_cuda_declaration_parser.py +60 -0
  19. simdref-0.0.19/tests/test_cuda_intrinsics.py +109 -0
  20. simdref-0.0.19/tests/test_cuda_td_expansion.py +108 -0
  21. simdref-0.0.19/tests/test_cuda_wiring.py +81 -0
  22. simdref-0.0.19/tests/test_issue_65_branch_target.py +91 -0
  23. simdref-0.0.19/tests/test_issue_65_foreign_arch.py +236 -0
  24. simdref-0.0.19/tests/test_issue_65_m5.py +22 -0
  25. simdref-0.0.19/tests/test_issue_65_sme_za.py +128 -0
  26. {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_arm_form_pick.py +3 -3
  27. simdref-0.0.19/tests/test_lsp_cuda.py +119 -0
  28. {simdref-0.0.17 → simdref-0.0.19}/tests/test_lsp_ptx.py +19 -0
  29. {simdref-0.0.17 → simdref-0.0.19}/tests/test_lsp_stdio.py +234 -18
  30. {simdref-0.0.17 → simdref-0.0.19}/LICENSE +0 -0
  31. {simdref-0.0.17 → simdref-0.0.19}/setup.cfg +0 -0
  32. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/__init__.py +0 -0
  33. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/__main__.py +0 -0
  34. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/arm_instructions.py +0 -0
  35. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/cli.py +0 -0
  36. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/export.py +0 -0
  37. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/htmltext.py +0 -0
  38. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/ingest.py +0 -0
  39. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/ingest_pdf.py +0 -0
  40. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/manpages.py +0 -0
  41. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/models.py +0 -0
  42. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/operands.py +0 -0
  43. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/pdfparse/__init__.py +0 -0
  44. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/pdfparse/base.py +0 -0
  45. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/pdfparse/intel.py +0 -0
  46. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/pdfparse/registry.py +0 -0
  47. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/pdfparse/types.py +0 -0
  48. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/pdfrefs.py +0 -0
  49. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf.py +0 -0
  50. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/__init__.py +0 -0
  51. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/amd_swopt.py +0 -0
  52. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/arm_swopt.py +0 -0
  53. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/cores.py +0 -0
  54. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/instlatx64.py +0 -0
  55. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/intel_guide.py +0 -0
  56. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/intel_optref.py +0 -0
  57. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/llvm_mca.py +0 -0
  58. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
  59. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/merge.py +0 -0
  60. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/ptx.py +0 -0
  61. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/queries.py +0 -0
  62. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/regtable.py +0 -0
  63. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/riscv.py +0 -0
  64. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/search.py +0 -0
  65. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/storage.py +0 -0
  66. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/tablecols.py +0 -0
  67. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/tui.py +0 -0
  68. {simdref-0.0.17 → simdref-0.0.19}/src/simdref/ui_labels.py +0 -0
  69. {simdref-0.0.17 → simdref-0.0.19}/src/simdref.egg-info/dependency_links.txt +0 -0
  70. {simdref-0.0.17 → simdref-0.0.19}/src/simdref.egg-info/entry_points.txt +0 -0
  71. {simdref-0.0.17 → simdref-0.0.19}/src/simdref.egg-info/requires.txt +0 -0
  72. {simdref-0.0.17 → simdref-0.0.19}/src/simdref.egg-info/top_level.txt +0 -0
  73. {simdref-0.0.17 → simdref-0.0.19}/tests/test_a1_sdm_parser.py +0 -0
  74. {simdref-0.0.17 → simdref-0.0.19}/tests/test_a2_perf_rule.py +0 -0
  75. {simdref-0.0.17 → simdref-0.0.19}/tests/test_a4_arm_pdfrefs.py +0 -0
  76. {simdref-0.0.17 → simdref-0.0.19}/tests/test_a5_offline_aarchmrs.py +0 -0
  77. {simdref-0.0.17 → simdref-0.0.19}/tests/test_amd_match_llvm_mc.py +0 -0
  78. {simdref-0.0.17 → simdref-0.0.19}/tests/test_annotate.py +0 -0
  79. {simdref-0.0.17 → simdref-0.0.19}/tests/test_annotate_positions.py +0 -0
  80. {simdref-0.0.17 → simdref-0.0.19}/tests/test_annotate_scans.py +0 -0
  81. {simdref-0.0.17 → simdref-0.0.19}/tests/test_arrangement_tail.py +0 -0
  82. {simdref-0.0.17 → simdref-0.0.19}/tests/test_asm_identity.py +0 -0
  83. {simdref-0.0.17 → simdref-0.0.19}/tests/test_asmline.py +0 -0
  84. {simdref-0.0.17 → simdref-0.0.19}/tests/test_audit_coverage.py +0 -0
  85. {simdref-0.0.17 → simdref-0.0.19}/tests/test_auto_update_on_version_change.py +0 -0
  86. {simdref-0.0.17 → simdref-0.0.19}/tests/test_b1_latency_pairs.py +0 -0
  87. {simdref-0.0.17 → simdref-0.0.19}/tests/test_b2_metadata.py +0 -0
  88. {simdref-0.0.17 → simdref-0.0.19}/tests/test_b3_sdm_forms.py +0 -0
  89. {simdref-0.0.17 → simdref-0.0.19}/tests/test_b4_descriptions_provenance.py +0 -0
  90. {simdref-0.0.17 → simdref-0.0.19}/tests/test_b5_linking.py +0 -0
  91. {simdref-0.0.17 → simdref-0.0.19}/tests/test_b5_perf_family.py +0 -0
  92. {simdref-0.0.17 → simdref-0.0.19}/tests/test_b5_sme.py +0 -0
  93. {simdref-0.0.17 → simdref-0.0.19}/tests/test_c0_perf_priority.py +0 -0
  94. {simdref-0.0.17 → simdref-0.0.19}/tests/test_cli_bare_query.py +0 -0
  95. {simdref-0.0.17 → simdref-0.0.19}/tests/test_cli_bootstrap_progress.py +0 -0
  96. {simdref-0.0.17 → simdref-0.0.19}/tests/test_cli_help.py +0 -0
  97. {simdref-0.0.17 → simdref-0.0.19}/tests/test_cli_llm.py +0 -0
  98. {simdref-0.0.17 → simdref-0.0.19}/tests/test_coverage_live.py +0 -0
  99. {simdref-0.0.17 → simdref-0.0.19}/tests/test_coverage_parity.py +0 -0
  100. {simdref-0.0.17 → simdref-0.0.19}/tests/test_display.py +0 -0
  101. {simdref-0.0.17 → simdref-0.0.19}/tests/test_display_scanners.py +0 -0
  102. {simdref-0.0.17 → simdref-0.0.19}/tests/test_export.py +0 -0
  103. {simdref-0.0.17 → simdref-0.0.19}/tests/test_export_viz.py +0 -0
  104. {simdref-0.0.17 → simdref-0.0.19}/tests/test_fetch_retry.py +0 -0
  105. {simdref-0.0.17 → simdref-0.0.19}/tests/test_filters.py +0 -0
  106. {simdref-0.0.17 → simdref-0.0.19}/tests/test_fixui_display.py +0 -0
  107. {simdref-0.0.17 → simdref-0.0.19}/tests/test_htmltext.py +0 -0
  108. {simdref-0.0.17 → simdref-0.0.19}/tests/test_ingest_enc_fields.py +0 -0
  109. {simdref-0.0.17 → simdref-0.0.19}/tests/test_ingest_sources.py +0 -0
  110. {simdref-0.0.17 → simdref-0.0.19}/tests/test_int_arm_isa_label.py +0 -0
  111. {simdref-0.0.17 → simdref-0.0.19}/tests/test_int_core_ids.py +0 -0
  112. {simdref-0.0.17 → simdref-0.0.19}/tests/test_int_ingest_registration.py +0 -0
  113. {simdref-0.0.17 → simdref-0.0.19}/tests/test_int_sdm_family_fallback.py +0 -0
  114. {simdref-0.0.17 → simdref-0.0.19}/tests/test_intel_operation_and_url.py +0 -0
  115. {simdref-0.0.17 → simdref-0.0.19}/tests/test_isa_detect_llvm_mc.py +0 -0
  116. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue2_fixes.py +0 -0
  117. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_51.py +0 -0
  118. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_52.py +0 -0
  119. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_53_54.py +0 -0
  120. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_55.py +0 -0
  121. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_56_57a.py +0 -0
  122. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_57_cpi.py +0 -0
  123. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_60_c1.py +0 -0
  124. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_60_contract.py +0 -0
  125. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_60_crossarch.py +0 -0
  126. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_60_matcher.py +0 -0
  127. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_65_arm_alias.py +0 -0
  128. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_65_compressed.py +0 -0
  129. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_65_identity.py +0 -0
  130. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_65_m1.py +0 -0
  131. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_65_perfdata.py +0 -0
  132. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_65_text.py +0 -0
  133. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_65_timing.py +0 -0
  134. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issues_23_24_25.py +0 -0
  135. {simdref-0.0.17 → simdref-0.0.19}/tests/test_issues_51_57_join.py +0 -0
  136. {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_arm_postindex.py +0 -0
  137. {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_att_movx.py +0 -0
  138. {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_autoupdate.py +0 -0
  139. {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_catalog_gaps.py +0 -0
  140. {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_ingest_isa_merge.py +0 -0
  141. {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_intel_size.py +0 -0
  142. {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_perf_join.py +0 -0
  143. {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_riscv_shared.py +0 -0
  144. {simdref-0.0.17 → simdref-0.0.19}/tests/test_lsp_web.py +0 -0
  145. {simdref-0.0.17 → simdref-0.0.19}/tests/test_models.py +0 -0
  146. {simdref-0.0.17 → simdref-0.0.19}/tests/test_no_catalog_row.py +0 -0
  147. {simdref-0.0.17 → simdref-0.0.19}/tests/test_objdump_listings.py +0 -0
  148. {simdref-0.0.17 → simdref-0.0.19}/tests/test_operands.py +0 -0
  149. {simdref-0.0.17 → simdref-0.0.19}/tests/test_pdfparse.py +0 -0
  150. {simdref-0.0.17 → simdref-0.0.19}/tests/test_perf.py +0 -0
  151. {simdref-0.0.17 → simdref-0.0.19}/tests/test_perf_source_amd_swopt.py +0 -0
  152. {simdref-0.0.17 → simdref-0.0.19}/tests/test_perf_source_arm_swopt.py +0 -0
  153. {simdref-0.0.17 → simdref-0.0.19}/tests/test_perf_source_instlatx64.py +0 -0
  154. {simdref-0.0.17 → simdref-0.0.19}/tests/test_perf_source_intel_guide.py +0 -0
  155. {simdref-0.0.17 → simdref-0.0.19}/tests/test_perf_source_intel_optref.py +0 -0
  156. {simdref-0.0.17 → simdref-0.0.19}/tests/test_perf_sources.py +0 -0
  157. {simdref-0.0.17 → simdref-0.0.19}/tests/test_presentation.py +0 -0
  158. {simdref-0.0.17 → simdref-0.0.19}/tests/test_preset_persistence.py +0 -0
  159. {simdref-0.0.17 → simdref-0.0.19}/tests/test_ptx.py +0 -0
  160. {simdref-0.0.17 → simdref-0.0.19}/tests/test_regtable.py +0 -0
  161. {simdref-0.0.17 → simdref-0.0.19}/tests/test_release_tag_pin.py +0 -0
  162. {simdref-0.0.17 → simdref-0.0.19}/tests/test_riscv_docs_pages.py +0 -0
  163. {simdref-0.0.17 → simdref-0.0.19}/tests/test_search.py +0 -0
  164. {simdref-0.0.17 → simdref-0.0.19}/tests/test_search_pushdown.py +0 -0
  165. {simdref-0.0.17 → simdref-0.0.19}/tests/test_shared_source_helpers.py +0 -0
  166. {simdref-0.0.17 → simdref-0.0.19}/tests/test_source_kind_filter.py +0 -0
  167. {simdref-0.0.17 → simdref-0.0.19}/tests/test_source_validation.py +0 -0
  168. {simdref-0.0.17 → simdref-0.0.19}/tests/test_storage_alnum.py +0 -0
  169. {simdref-0.0.17 → simdref-0.0.19}/tests/test_storage_payload.py +0 -0
  170. {simdref-0.0.17 → simdref-0.0.19}/tests/test_storage_schema.py +0 -0
  171. {simdref-0.0.17 → simdref-0.0.19}/tests/test_tablecols.py +0 -0
  172. {simdref-0.0.17 → simdref-0.0.19}/tests/test_tui.py +0 -0
  173. {simdref-0.0.17 → simdref-0.0.19}/tests/test_ui_labels_parity.py +0 -0
  174. {simdref-0.0.17 → simdref-0.0.19}/tests/test_update_build_stamp.py +0 -0
  175. {simdref-0.0.17 → simdref-0.0.19}/tests/test_vendor_riscv_categories.py +0 -0
  176. {simdref-0.0.17 → simdref-0.0.19}/tests/test_vendor_riscv_sources.py +0 -0
  177. {simdref-0.0.17 → simdref-0.0.19}/tests/test_version_matches_pyproject.py +0 -0
  178. {simdref-0.0.17 → simdref-0.0.19}/tests/test_x86_key_widths.py +0 -0
  179. {simdref-0.0.17 → simdref-0.0.19}/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.17
3
+ Version: 0.0.19
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
@@ -177,7 +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 | 1,088 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 |
181
182
  | Intel Guide perf tables | Per-core latency and throughput | 9,134 rows |
182
183
  | Intel Optimization Ref. | Latency and throughput tables (volume 2) | 3,155 rows |
183
184
  | InstLatX64 | Measured latency and throughput dumps | 16,235 rows |
@@ -142,7 +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 | 1,088 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 |
146
147
  | Intel Guide perf tables | Per-core latency and throughput | 9,134 rows |
147
148
  | Intel Optimization Ref. | Latency and throughput tables (volume 2) | 3,155 rows |
148
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.17"
7
+ version = "0.0.19"
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"
@@ -19,8 +19,17 @@ from typing import Any, Iterable, Iterator
19
19
  from simdref import perf
20
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(
@@ -616,6 +626,42 @@ def _voter_names():
616
626
 
617
627
  _EXCL_ARM, _SHARED, _RISCV = _voter_names()
618
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
+
619
665
 
620
666
  def _isa_votes(text: str) -> tuple[int, int, int]:
621
667
  """(excl_arm, excl_riscv, shared) register votes of one instruction text."""
@@ -630,7 +676,9 @@ def _isa_votes(text: str) -> tuple[int, int, int]:
630
676
  while j < n and (text[j] == "_" or text[j].isalnum()):
631
677
  j += 1
632
678
  token = text[i:j]
633
- 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 "%$":
634
682
  if token in _EXCL_ARM:
635
683
  excl_arm += 1
636
684
  elif token in _SHARED:
@@ -1047,6 +1095,19 @@ def pick_record(
1047
1095
  # AT&T lists sources first; catalog form tokens are in Intel order.
1048
1096
  slots = list(reversed(slots))
1049
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
+
1050
1111
  def score(rec: InstructionRecord) -> tuple[int, int, int, int]:
1051
1112
  measured = sum(
1052
1113
  1
@@ -1063,6 +1124,49 @@ def pick_record(
1063
1124
  return max(candidates, key=score)
1064
1125
 
1065
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
+
1066
1170
  # ---------------------------------------------------------------------------
1067
1171
  # Aggregation
1068
1172
  # ---------------------------------------------------------------------------
@@ -1439,12 +1543,15 @@ def _annotate_instruction(
1439
1543
  line_isa: str | None = None,
1440
1544
  line_number: int | None = None,
1441
1545
  identity_cache: dict[str, tuple[InstructionRecord, PerfSummary | None]] | None = None,
1546
+ foreign: bool = False,
1442
1547
  ) -> tuple[str, dict[str, Any] | None]:
1443
1548
  """Return the rendered output line and an optional JSON record.
1444
1549
 
1445
1550
  ``line_isa`` names the family the line's own evidence shows (a pinned
1446
1551
  ``--isa`` counts as one). None marks a neutral line in auto mode: the
1447
- 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).
1448
1555
  """
1449
1556
  if stats is not None:
1450
1557
  stats["parsed"] = stats.get("parsed", 0) + 1
@@ -1509,7 +1616,9 @@ def _annotate_instruction(
1509
1616
  else:
1510
1617
  isa = line_isa or opts.isa
1511
1618
  pick_isa = isa
1512
- if lookup_cache is not None:
1619
+ if foreign:
1620
+ records = []
1621
+ elif lookup_cache is not None:
1513
1622
  records = lookup_cache.get((parsed.mnemonic, isa))
1514
1623
  if records is None:
1515
1624
  # Module-level call (not a captured reference) so tests can
@@ -1518,16 +1627,21 @@ def _annotate_instruction(
1518
1627
  lookup_cache[(parsed.mnemonic, isa)] = records
1519
1628
  else:
1520
1629
  records = lookup(parsed.mnemonic, conn, isa=isa)
1521
- if not records and line_isa is None:
1630
+ if not records and line_isa is None and not foreign:
1522
1631
  # A neutral line whose stream family holds no rows gets the rows
1523
1632
  # of the family that has them (issue #57 batch: jnle, bare b).
1633
+ # A foreign line borrows nothing (issue #65 B2/B6).
1524
1634
  pick_isa = None
1525
1635
  records = lookup(parsed.mnemonic, conn, isa=None)
1526
1636
  if lookup_cache is not None:
1527
1637
  lookup_cache[(parsed.mnemonic, None)] = records
1528
- if stats is not None and records:
1529
- stats["recognized"] = stats.get("recognized", 0) + 1
1530
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
1531
1645
 
1532
1646
  if record is None:
1533
1647
  if opts.unknown == "drop":
@@ -1686,6 +1800,7 @@ def annotate_stream(
1686
1800
  }
1687
1801
  auto_isa = opts.isa not in ("x86", "arm", "riscv")
1688
1802
  line_family: dict[int, str] = {}
1803
+ foreign: set[int] = set()
1689
1804
  if auto_isa:
1690
1805
  # Assembler acceptance decides each line's family: one llvm-mc run
1691
1806
  # per candidate triple. The register vote below is the fallback when
@@ -1693,6 +1808,13 @@ def annotate_stream(
1693
1808
  probe_texts = [text for _index, text in items]
1694
1809
  families, majority = isa_by_acceptance(probe_texts)
1695
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
+ }
1696
1818
  # auto: the acceptance majority names the ISA, then the input
1697
1819
  # signals, then the document arch from _document_arch; x86 stays
1698
1820
  # the guess when all stay silent.
@@ -1729,6 +1851,11 @@ def annotate_stream(
1729
1851
  if not collecting_json:
1730
1852
  yield parsed.raw + "\n"
1731
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}")
1732
1859
  out_line, record = _annotate_instruction(
1733
1860
  parsed,
1734
1861
  opts,
@@ -1736,13 +1863,10 @@ def annotate_stream(
1736
1863
  lookup_cache,
1737
1864
  stats,
1738
1865
  identity_key=identity.get(index),
1739
- # A pin forces one family; auto mode splits the batch per line,
1740
- # the assembler verdict first and the register vote as fallback.
1741
- line_isa=opts.isa
1742
- if not auto_isa
1743
- else line_family.get(index) or _line_isa(f"{parsed.mnemonic} {parsed.operands}"),
1866
+ line_isa=line_isa,
1744
1867
  line_number=index + 1,
1745
1868
  identity_cache=identity_cache,
1869
+ foreign=index in foreign if line_isa is None else False,
1746
1870
  )
1747
1871
  if collecting_json:
1748
1872
  if record is not None:
@@ -1403,6 +1403,40 @@ _ISA_CANDIDATES = (
1403
1403
  )
1404
1404
 
1405
1405
 
1406
+ def _reloc_branch_targets(text: str) -> str:
1407
+ """Spell objdump's bare branch targets the fixup way ("je 7f37b" as ".+0x7f37b").
1408
+
1409
+ objdump prints branch targets as bare hex, and llvm-mc rejects the bare
1410
+ spelling where the value holds an a-f letter: "7f37b" is no decimal
1411
+ number and no valid symbol. The ".+0x" spelling is the same PC-relative
1412
+ fixup the <sym> form answers with, and it carries no immediate range
1413
+ check, so it assembles on every candidate triple for any address. A
1414
+ token qualifies only when it starts with a digit, holds only hex digits
1415
+ with at least one a-f letter (a decimal immediate assembles as-is), and
1416
+ follows a separator, so the tail of "0x...", "0b..." or "#0x..." text is
1417
+ never rewritten.
1418
+ """
1419
+ hexd = "0123456789abcdefABCDEF"
1420
+ letters = "abcdefABCDEF"
1421
+ out: list[str] = []
1422
+ i = 0
1423
+ n = len(text)
1424
+ while i < n:
1425
+ if text[i].isdigit() and (i == 0 or text[i - 1] in " \t,"):
1426
+ j = i
1427
+ while j < n and text[j] in hexd:
1428
+ j += 1
1429
+ token = text[i:j]
1430
+ if any(c in letters for c in token):
1431
+ out.append(".+0x")
1432
+ out.append(token)
1433
+ i = j
1434
+ continue
1435
+ out.append(text[i])
1436
+ i += 1
1437
+ return "".join(out)
1438
+
1439
+
1406
1440
  def _probe_all_triples(lines: list[str], llvm_mc: str) -> dict[tuple[str, str], list[dict]]:
1407
1441
  """Probe every candidate triple, one llvm-mc run per triple.
1408
1442
 
@@ -1412,6 +1446,7 @@ def _probe_all_triples(lines: list[str], llvm_mc: str) -> dict[tuple[str, str],
1412
1446
  """
1413
1447
  from concurrent.futures import ThreadPoolExecutor
1414
1448
 
1449
+ lines = [_reloc_branch_targets(line) for line in lines]
1415
1450
  with ThreadPoolExecutor(max_workers=len(_ISA_CANDIDATES)) as pool:
1416
1451
  futures = {
1417
1452
  (arch, syntax): pool.submit(_probe_lines_split, lines, arch, syntax, llvm_mc)
@@ -174,28 +174,96 @@ def c_asm_zone_at(text: str, offset: int) -> str | None:
174
174
 
175
175
  'template' is an asm(...) template string, 'msvc' an MSVC __asm block
176
176
  or line, 'operands' the operand and clobber lists after the first ':'
177
- of an asm(...) call. None is plain code. Shares _asm_call_at with
177
+ of an asm(...) call, 'comment' a C comment, 'string' a string literal
178
+ that is no asm template. None is plain code. Shares _asm_call_at with
178
179
  c_asm_literals, so hover and completion parse the same calls.
179
180
  """
181
+ span = c_span_at(text, offset)
182
+ if span == "comment":
183
+ return span
184
+ zone = c_asm_zone_span_at(text, offset)
185
+ return zone[0] if zone is not None else span
186
+
187
+
188
+ def c_asm_zone_span_at(text: str, offset: int) -> tuple[str, int, int] | None:
189
+ """(kind, low, high) of the asm zone holding the offset, or None.
190
+
191
+ The same walk as c_asm_zone_at. lsp.py reads the bounds: each MSVC
192
+ __asm statement starts at its own zone's low bound.
193
+ """
180
194
  masked = _mask_c_comments(text)
181
195
  index = 0
182
196
  while index < len(masked):
183
197
  hit = _asm_call_at(masked, index)
184
198
  if hit is not None:
185
199
  block_end, literals, zones = hit
186
- for kind, low, high in zones:
187
- if low <= offset <= high:
188
- return kind
189
- for start, _body in literals:
190
- if start <= offset <= start + len(_body):
191
- return "template"
200
+ for zone in zones:
201
+ if zone[1] <= offset <= zone[2]:
202
+ return zone
203
+ for start, body in literals:
204
+ if start <= offset <= start + len(body):
205
+ return "template", start, start + len(body)
192
206
  index = block_end
193
207
  else:
194
208
  index += 1
195
209
  return None
196
210
 
197
211
 
212
+ def c_span_at(text: str, offset: int) -> str | None:
213
+ """'comment' or 'string' when a cursor offset sits inside one, else None.
214
+
215
+ Same scan as _mask_c_comments. A cursor right after the opening
216
+ delimiter is inside; a cursor after the closing one is outside. An
217
+ unterminated block comment runs to the end, an unterminated string to
218
+ the end of its line: both are still being typed. A char literal and a
219
+ raw string are their own tokens: the double quote inside the char
220
+ literal '"' never opens a string, and a raw string keeps its quotes.
221
+ """
222
+ index = 0
223
+ while index < offset:
224
+ line_end = text.find("\n", index)
225
+ line_end = len(text) if line_end == -1 else line_end
226
+ if text[index] == "'":
227
+ end = _char_end(text, index) or line_end + 1
228
+ if index < offset < end:
229
+ return "string"
230
+ elif text[index] == '"':
231
+ end = _raw_string_end(text, index)
232
+ if end is None:
233
+ end = _string_end(text, index) or line_end + 1
234
+ if index < offset < end:
235
+ return "string"
236
+ elif text.startswith("//", index):
237
+ end = line_end
238
+ if index + 2 <= offset <= end:
239
+ return "comment"
240
+ elif text.startswith("/*", index):
241
+ close = text.find("*/", index + 2)
242
+ end = len(text) if close == -1 else close + 2
243
+ if index + 2 <= offset <= (end if close == -1 else close):
244
+ return "comment"
245
+ else:
246
+ index += 1
247
+ continue
248
+ index = max(end, index + 1)
249
+ return None
250
+
251
+
198
252
  _QUALIFIERS = ("volatile", "__volatile__", "goto", "inline")
253
+ _ASM_KEYWORDS = ("__asm__", "__asm", "asm")
254
+
255
+
256
+ def _asm_keyword_at(text: str, index: int) -> str | None:
257
+ """The asm keyword at index with word boundaries on both sides, or None."""
258
+ if index > 0 and (text[index - 1].isalnum() or text[index - 1] == "_"):
259
+ return None
260
+ for keyword in _ASM_KEYWORDS:
261
+ if text.startswith(keyword, index):
262
+ after = index + len(keyword)
263
+ tail = text[after : after + 1]
264
+ if not (tail.isalnum() or tail == "_"):
265
+ return keyword
266
+ return None
199
267
 
200
268
 
201
269
  def _asm_call_at(
@@ -214,7 +282,7 @@ def _asm_call_at(
214
282
  and (text[index - 1].isalnum() or text[index - 1] == "_")
215
283
  ):
216
284
  return None
217
- for keyword in ("__asm__", "__asm", "asm"):
285
+ for keyword in _ASM_KEYWORDS:
218
286
  if text.startswith(keyword, index):
219
287
  break
220
288
  else:
@@ -240,9 +308,12 @@ def _asm_call_at(
240
308
  return None
241
309
  return close + 1, [], [("msvc", cursor + 1, close)]
242
310
  if text[cursor : cursor + 1] != "(":
243
- # MSVC __asm <insn> line: the instruction runs to end of line.
244
- end = index
311
+ # MSVC __asm <insn>: the statement runs to end of line, or to the
312
+ # next __asm keyword, which starts its own statement on the row.
313
+ end = cursor
245
314
  while end < len(text) and text[end] != "\n":
315
+ if _asm_keyword_at(text, end):
316
+ break
246
317
  end += 1
247
318
  return end, [], [("msvc", cursor, end)]
248
319
  cursor = _skip_space(text, cursor + 1)
@@ -353,14 +424,63 @@ def _string_end(text: str, index: int, spans_lines: bool = False) -> int | None:
353
424
  return None
354
425
 
355
426
 
427
+ def _char_end(text: str, index: int) -> int | None:
428
+ """The index after the closing quote of the char literal at index, or None.
429
+
430
+ A char literal is one token like a string: its quote never opens a
431
+ string, and an escape pair ("\\'", "\\"") counts as one character.
432
+ """
433
+ if text[index : index + 1] != "'":
434
+ return None
435
+ cursor = index + 1
436
+ while cursor < len(text):
437
+ char = text[cursor]
438
+ if char == "\\":
439
+ cursor += 2
440
+ elif char == "'":
441
+ return cursor + 1
442
+ elif char == "\n":
443
+ return None
444
+ else:
445
+ cursor += 1
446
+ return None
447
+
448
+
449
+ def _raw_string_end(text: str, index: int) -> int | None:
450
+ """The index after the R"delim(...)delim" raw string whose quote is at index.
451
+
452
+ None when the shape is not a raw string: the caller then reads the
453
+ quote as a plain string. An unterminated raw string runs one past the
454
+ end of the text, because its body spans lines.
455
+ """
456
+ if text[index - 1 : index] != "R":
457
+ return None
458
+ open_paren = text.find("(", index + 1, index + 18)
459
+ if open_paren == -1:
460
+ return None
461
+ delim = text[index + 1 : open_paren]
462
+ if len(delim) > 16 or any(char.isspace() or char in "()\\" for char in delim):
463
+ return None
464
+ close = text.find(")" + delim + '"', open_paren + 1)
465
+ return len(text) + 1 if close == -1 else close + len(delim) + 2
466
+
467
+
356
468
  def _mask_c_comments(text: str) -> str:
357
- """Blank C comments to spaces (newlines stay). String literals stay."""
469
+ """Blank C comments to spaces (newlines stay). Literal tokens stay."""
358
470
  out: list[str] = []
359
471
  index = 0
360
472
  while index < len(text):
361
473
  char = text[index]
474
+ if char == "'":
475
+ end = _char_end(text, index)
476
+ if end is not None:
477
+ out.append(text[index:end])
478
+ index = end
479
+ continue
362
480
  if char == '"':
363
- end = _string_end(text, index)
481
+ end = _raw_string_end(text, index)
482
+ if end is None:
483
+ end = _string_end(text, index)
364
484
  if end is not None:
365
485
  out.append(text[index:end])
366
486
  index = end
@@ -68,6 +68,7 @@ ISA_FAMILY = {
68
68
  "riscv32": "riscv",
69
69
  "riscv64": "riscv",
70
70
  "ptx": "ptx",
71
+ "cuda": "cuda",
71
72
  }
72
73
 
73
74
 
@@ -84,6 +85,8 @@ def isa_family(architecture: str | None) -> str | None:
84
85
  return "riscv"
85
86
  if name.startswith("ptx"):
86
87
  return "ptx"
88
+ if name.startswith("cuda"):
89
+ return "cuda"
87
90
  return "x86"
88
91
 
89
92
 
@@ -150,6 +153,35 @@ def _first_digits(text: str) -> int | None:
150
153
  return int(text[start:end]) if end > start else None
151
154
 
152
155
 
156
+ def names_za_tile(text: str) -> bool:
157
+ """True when *text* names a ZA tile operand ("za0h", "za.s"), never ZT0.
158
+
159
+ A ZA tile name starts with "za" at an operand boundary and closes the
160
+ stem with a tile digit ("za0h"), a tile element letter ("zab") or the
161
+ full-register dot ("za.s"). One boundary scan; no regular expressions.
162
+ """
163
+ low = text.casefold()
164
+ for at in range(len(low) - 2):
165
+ if low[at] != "z" or low[at + 1] != "a":
166
+ continue
167
+ if at > 0 and (low[at - 1].isalnum() or low[at - 1] in "._"):
168
+ continue
169
+ if low[at + 2].isdecimal() or low[at + 2] in ".bhwdq":
170
+ return True
171
+ return False
172
+
173
+
174
+ def key_names_za_tile(key: str) -> bool:
175
+ """True when a catalog key names a ZA tile operand form.
176
+
177
+ The tile encodes as a placeholder ("<ZAT...>"), the tile-slice register
178
+ ("<WV>") or a literal name ("ZA0<HV>"). ZT0 rows stay out: SVE counted
179
+ register-list keys reuse "<ZT..>" placeholder names for plain Z lists.
180
+ """
181
+ upper = key.upper()
182
+ return "<ZA" in upper or "<WV>" in upper or names_za_tile(key)
183
+
184
+
153
185
  def form_accept(op: str) -> frozenset:
154
186
  """The line classes a catalog-key operand accepts, as one frozenset.
155
187
 
@@ -200,8 +232,10 @@ def form_accept(op: str) -> frozenset:
200
232
  name = _cut_key_repeats(op.upper())
201
233
  if "<HASH>" in name:
202
234
  return IMMEDIATE_ACCEPT
203
- # A ZA tile operand ("<ZAT...>[W1, #0]") is not a plain vector register.
204
- if "<ZAT" in name:
235
+ # A ZA tile operand ("<ZAT...>[W1, #0]", "ZA0<HV>.B[W1, #0]",
236
+ # "Z3.4S[<WV>, #0]") is not a plain vector register: it matches a line's
237
+ # ZA tile operand only.
238
+ if key_names_za_tile(op):
205
239
  return frozenset({"za"})
206
240
  if _is_counted_vector(name):
207
241
  # A counted register list accepts only the same count.
@@ -0,0 +1,50 @@
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.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ from typing import Any
12
+
13
+ from simdref.models import IntrinsicRecord
14
+
15
+ BUNDLE_FORMAT = "clang-cuda-headers-v2"
16
+ CUDA_INTRINSICS_DOC_URL = "https://docs.nvidia.com/cuda/cuda-c-programming-guide/"
17
+
18
+
19
+ def parse_cuda_intrinsics_payload(text: str) -> list[IntrinsicRecord]:
20
+ """Parse the CUDA intrinsics JSON bundle into IntrinsicRecord rows."""
21
+ data = json.loads(text)
22
+ if data.get("format") != BUNDLE_FORMAT:
23
+ raise ValueError(f"Unexpected bundle format: {data.get('format')!r}")
24
+
25
+ rows: list[IntrinsicRecord] = []
26
+ for item in data.get("intrinsics", []):
27
+ metadata = item.get("metadata", {})
28
+ classification = metadata.get("classification", "")
29
+
30
+ row = IntrinsicRecord(
31
+ name=item["name"],
32
+ signature=item.get("signature", ""),
33
+ description=item.get("description", ""),
34
+ header=item.get("header", ""),
35
+ url=item.get("url", ""),
36
+ architecture="cuda",
37
+ isa=["CUDA"],
38
+ category=item.get("category", ""),
39
+ subcategory=item.get("subcategory", ""),
40
+ instructions=item.get("instructions", []),
41
+ instruction_refs=item.get("instruction_refs", []),
42
+ metadata={"classification": classification},
43
+ doc_sections=item.get("doc_sections", {}),
44
+ notes=item.get("notes", []),
45
+ aliases=item.get("aliases", []),
46
+ source="clang-cuda-headers",
47
+ )
48
+ rows.append(row)
49
+
50
+ return rows