simdref 0.0.18__tar.gz → 0.0.20__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {simdref-0.0.18/src/simdref.egg-info → simdref-0.0.20}/PKG-INFO +1 -1
- {simdref-0.0.18 → simdref-0.0.20}/pyproject.toml +1 -1
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/asmline.py +132 -12
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/cli.py +52 -16
- simdref-0.0.20/src/simdref/cuda.py +294 -0
- simdref-0.0.20/src/simdref/cuda_docs.py +1158 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/display.py +15 -14
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/export.py +42 -26
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/filters.py +190 -4
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/ingest_catalog.py +103 -3
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/ingest_sources.py +32 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/lsp.py +125 -12
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/ptx.py +279 -17
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/tui.py +549 -24
- simdref-0.0.20/src/simdref/urls.py +46 -0
- {simdref-0.0.18 → simdref-0.0.20/src/simdref.egg-info}/PKG-INFO +1 -1
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref.egg-info/SOURCES.txt +22 -0
- simdref-0.0.20/tests/test_annotate_microarch.py +55 -0
- simdref-0.0.20/tests/test_arm_arch_filter.py +159 -0
- simdref-0.0.20/tests/test_category_filter.py +84 -0
- simdref-0.0.20/tests/test_cli_perf_line.py +37 -0
- simdref-0.0.20/tests/test_cuda_docs_guide.py +140 -0
- simdref-0.0.20/tests/test_cuda_docs_math.py +382 -0
- simdref-0.0.20/tests/test_cuda_floors.py +145 -0
- simdref-0.0.20/tests/test_cuda_join.py +145 -0
- simdref-0.0.20/tests/test_cuda_links.py +75 -0
- simdref-0.0.20/tests/test_cuda_tex.py +72 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_cuda_wiring.py +24 -0
- simdref-0.0.20/tests/test_detail_metadata.py +90 -0
- simdref-0.0.20/tests/test_empty_query_state.py +69 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_export.py +21 -20
- simdref-0.0.20/tests/test_export_sm.py +72 -0
- simdref-0.0.20/tests/test_export_sm_column.py +86 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_filters.py +21 -3
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_lsp_ptx.py +19 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_lsp_stdio.py +212 -3
- simdref-0.0.20/tests/test_perf_kind_toggle.py +84 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_presentation.py +10 -0
- simdref-0.0.20/tests/test_preset_chip_sync.py +45 -0
- simdref-0.0.20/tests/test_ptx.py +431 -0
- simdref-0.0.20/tests/test_related_entries.py +121 -0
- simdref-0.0.20/tests/test_result_row_badges.py +71 -0
- simdref-0.0.20/tests/test_sm_filter.py +297 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_tui.py +163 -0
- simdref-0.0.20/tests/test_urls.py +71 -0
- simdref-0.0.18/src/simdref/cuda.py +0 -50
- simdref-0.0.18/tests/test_ptx.py +0 -253
- {simdref-0.0.18 → simdref-0.0.20}/LICENSE +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/README.md +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/setup.cfg +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/__init__.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/__main__.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/annotate.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/arm_instructions.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/asm_identity.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/catkey.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/htmltext.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/ingest.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/ingest_pdf.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/manpages.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/models.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/operands.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/pdfparse/__init__.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/pdfparse/base.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/pdfparse/intel.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/pdfparse/registry.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/pdfparse/types.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/pdfrefs.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/__init__.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/amd_swopt.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/arm_swopt.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/cores.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/instlatx64.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/intel_guide.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/intel_optref.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/llvm_mca.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/merge.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/queries.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/regtable.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/riscv.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/search.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/storage.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/tablecols.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref/ui_labels.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref.egg-info/dependency_links.txt +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref.egg-info/entry_points.txt +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref.egg-info/requires.txt +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/src/simdref.egg-info/top_level.txt +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_a1_sdm_parser.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_a2_perf_rule.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_a4_arm_pdfrefs.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_a5_offline_aarchmrs.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_amd_match_llvm_mc.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_annotate.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_annotate_positions.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_annotate_scans.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_arrangement_tail.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_asm_identity.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_asm_wire.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_asmline.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_audit_coverage.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_auto_update_on_version_change.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_b1_latency_pairs.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_b2_metadata.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_b3_sdm_forms.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_b4_descriptions_provenance.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_b5_linking.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_b5_perf_family.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_b5_sme.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_c0_perf_priority.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_catkey.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_cli_bare_query.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_cli_bootstrap_progress.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_cli_help.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_cli_llm.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_coverage_live.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_coverage_parity.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_cuda_declaration_parser.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_cuda_intrinsics.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_cuda_td_expansion.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_display.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_display_scanners.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_export_viz.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_fetch_retry.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_fixui_display.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_htmltext.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_ingest_enc_fields.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_ingest_sources.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_int_arm_isa_label.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_int_core_ids.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_int_ingest_registration.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_int_sdm_family_fallback.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_intel_operation_and_url.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_isa_detect_llvm_mc.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue2_fixes.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_51.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_52.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_53_54.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_55.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_56_57a.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_57_cpi.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_60_c1.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_60_contract.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_60_crossarch.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_60_matcher.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_arm_alias.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_branch_target.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_compressed.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_foreign_arch.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_identity.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_m1.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_m5.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_perfdata.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_sme_za.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_text.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_timing.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issues_23_24_25.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_issues_51_57_join.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_arm_form_pick.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_arm_postindex.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_att_movx.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_autoupdate.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_catalog_gaps.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_ingest_isa_merge.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_intel_size.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_perf_join.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_riscv_shared.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_lsp_cuda.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_lsp_web.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_models.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_no_catalog_row.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_objdump_listings.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_operands.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_pdfparse.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_perf.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_perf_source_amd_swopt.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_perf_source_arm_swopt.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_perf_source_instlatx64.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_perf_source_intel_guide.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_perf_source_intel_optref.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_perf_sources.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_preset_persistence.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_regtable.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_release_tag_pin.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_riscv_docs_pages.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_search.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_search_pushdown.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_shared_source_helpers.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_source_kind_filter.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_source_validation.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_storage_alnum.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_storage_payload.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_storage_schema.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_tablecols.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_ui_labels_parity.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_update_build_stamp.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_vendor_riscv_categories.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_vendor_riscv_sources.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_version_matches_pyproject.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_x86_key_widths.py +0 -0
- {simdref-0.0.18 → simdref-0.0.20}/tests/test_x86_linking.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "simdref"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.20"
|
|
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"
|
|
@@ -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
|
|
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
|
|
187
|
-
if
|
|
188
|
-
return
|
|
189
|
-
for start,
|
|
190
|
-
if start <= offset <= start + len(
|
|
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
|
|
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
|
|
244
|
-
|
|
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).
|
|
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 =
|
|
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
|
|
@@ -917,11 +917,13 @@ def _payload_source_kinds(arch_details) -> list[str]:
|
|
|
917
917
|
# ---------------------------------------------------------------------------
|
|
918
918
|
|
|
919
919
|
|
|
920
|
-
def _print_search_results_runtime(
|
|
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.
|
|
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
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
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),
|
|
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,294 @@
|
|
|
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.models import IntrinsicRecord
|
|
17
|
+
from simdref.urls import resolve_url
|
|
18
|
+
|
|
19
|
+
BUNDLE_FORMAT = "clang-cuda-headers-v2"
|
|
20
|
+
CUDA_INTRINSICS_DOC_URL = "https://docs.nvidia.com/cuda/cuda-c-programming-guide/"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_cuda_intrinsics_payload(text: str) -> list[IntrinsicRecord]:
|
|
24
|
+
"""Parse the CUDA intrinsics JSON bundle into IntrinsicRecord rows."""
|
|
25
|
+
data = json.loads(text)
|
|
26
|
+
if data.get("format") != BUNDLE_FORMAT:
|
|
27
|
+
raise ValueError(f"Unexpected bundle format: {data.get('format')!r}")
|
|
28
|
+
|
|
29
|
+
rows: list[IntrinsicRecord] = []
|
|
30
|
+
for item in data.get("intrinsics", []):
|
|
31
|
+
metadata = item.get("metadata", {})
|
|
32
|
+
classification = metadata.get("classification", "")
|
|
33
|
+
record_metadata = {"classification": classification}
|
|
34
|
+
for key in ("min_sm", "min_ptx", "min_sm_from", "min_ptx_from"):
|
|
35
|
+
value = str(item.get(key) or metadata.get(key) or "").strip()
|
|
36
|
+
if value:
|
|
37
|
+
record_metadata[key] = value
|
|
38
|
+
|
|
39
|
+
row = IntrinsicRecord(
|
|
40
|
+
name=item["name"],
|
|
41
|
+
signature=item.get("signature", ""),
|
|
42
|
+
description=item.get("description", ""),
|
|
43
|
+
header=item.get("header", ""),
|
|
44
|
+
url=item.get("url", ""),
|
|
45
|
+
architecture="cuda",
|
|
46
|
+
isa=["CUDA"],
|
|
47
|
+
category=item.get("category", ""),
|
|
48
|
+
subcategory=item.get("subcategory", ""),
|
|
49
|
+
instructions=item.get("instructions", []),
|
|
50
|
+
instruction_refs=item.get("instruction_refs", []),
|
|
51
|
+
metadata=record_metadata,
|
|
52
|
+
doc_sections=item.get("doc_sections", {}),
|
|
53
|
+
notes=item.get("notes", []),
|
|
54
|
+
aliases=item.get("aliases", []),
|
|
55
|
+
source="clang-cuda-headers",
|
|
56
|
+
)
|
|
57
|
+
rows.append(row)
|
|
58
|
+
|
|
59
|
+
return rows
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def parse_cuda_calls(text: str) -> dict[str, list[str]]:
|
|
63
|
+
"""name to call-name list from the bundle call graph.
|
|
64
|
+
|
|
65
|
+
The vendor step records, per header function, the names its body
|
|
66
|
+
calls. This is the graph the doc join and the SM floors walk.
|
|
67
|
+
"""
|
|
68
|
+
data = json.loads(text)
|
|
69
|
+
if data.get("format") != BUNDLE_FORMAT:
|
|
70
|
+
raise ValueError(f"Unexpected bundle format: {data.get('format')!r}")
|
|
71
|
+
out: dict[str, list[str]] = {}
|
|
72
|
+
for item in data.get("intrinsics", []):
|
|
73
|
+
out[item["name"]] = [str(name) for name in item.get("calls") or []]
|
|
74
|
+
return out
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _signature_params(signature: str) -> str:
|
|
78
|
+
"""The argument list of a signature, its parameter names and types.
|
|
79
|
+
|
|
80
|
+
The math pages carry no Parameters field. The signature does. This
|
|
81
|
+
takes the text between the first "(" and the last ")" of it.
|
|
82
|
+
"""
|
|
83
|
+
start = signature.find("(")
|
|
84
|
+
end = signature.rfind(")")
|
|
85
|
+
if start == -1 or end <= start:
|
|
86
|
+
return ""
|
|
87
|
+
args = " ".join(signature[start + 1 : end].split())
|
|
88
|
+
if not args or args == "void":
|
|
89
|
+
return ""
|
|
90
|
+
return args
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _first_sentence(text: str) -> str:
|
|
94
|
+
"""The first sentence of a description, with its full stop."""
|
|
95
|
+
joined = " ".join(text.split())
|
|
96
|
+
if not joined:
|
|
97
|
+
return ""
|
|
98
|
+
stop = joined.find(". ")
|
|
99
|
+
if stop == -1:
|
|
100
|
+
return joined
|
|
101
|
+
return joined[: stop + 1]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _stripped(name: str) -> str:
|
|
105
|
+
"""A name as one lowercase run of characters, for containment."""
|
|
106
|
+
return name.replace("_", "").lower()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _reachable_builtins(name: str, calls: dict[str, list[str]]) -> set[str]:
|
|
110
|
+
"""The __nvvm_ and __nv_ tokens the name reaches over the call graph."""
|
|
111
|
+
seen = {name}
|
|
112
|
+
stack = [name]
|
|
113
|
+
out: set[str] = set()
|
|
114
|
+
while stack:
|
|
115
|
+
current = stack.pop()
|
|
116
|
+
for token in calls.get(current, []):
|
|
117
|
+
if token.startswith("__nvvm_") or token.startswith("__nv_"):
|
|
118
|
+
out.add(token)
|
|
119
|
+
elif token in calls and token not in seen:
|
|
120
|
+
seen.add(token)
|
|
121
|
+
stack.append(token)
|
|
122
|
+
return out
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _nearest_documented_caller(name: str, calls: dict[str, list[str]], documented: set[str]) -> str:
|
|
126
|
+
"""The nearest documented name that calls this one, else "".
|
|
127
|
+
|
|
128
|
+
Breadth first over the caller side of the call graph. The nearest
|
|
129
|
+
level with a documented name decides. Ties take the shortest name.
|
|
130
|
+
"""
|
|
131
|
+
callers: dict[str, list[str]] = {}
|
|
132
|
+
for caller, callees in calls.items():
|
|
133
|
+
for callee in callees:
|
|
134
|
+
callers.setdefault(callee, []).append(caller)
|
|
135
|
+
seen = {name}
|
|
136
|
+
frontier = [name]
|
|
137
|
+
while frontier:
|
|
138
|
+
next_frontier: list[str] = []
|
|
139
|
+
candidates: list[str] = []
|
|
140
|
+
for current in frontier:
|
|
141
|
+
for caller in callers.get(current, []):
|
|
142
|
+
if caller in documented and caller not in candidates:
|
|
143
|
+
candidates.append(caller)
|
|
144
|
+
if caller not in seen:
|
|
145
|
+
seen.add(caller)
|
|
146
|
+
next_frontier.append(caller)
|
|
147
|
+
if candidates:
|
|
148
|
+
return min(candidates, key=lambda item: (len(item), item))
|
|
149
|
+
frontier = next_frontier
|
|
150
|
+
return ""
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _contained_doc_name(
|
|
154
|
+
name: str, calls: dict[str, list[str]], documented: dict[str, tuple[dict, str]]
|
|
155
|
+
) -> str:
|
|
156
|
+
"""A documented name that this name reaches by name containment.
|
|
157
|
+
|
|
158
|
+
Last join tier. The documented name, without underscores, must sit
|
|
159
|
+
inside the row name or inside one of the builtins the row reaches,
|
|
160
|
+
from a word start of that name. The row name is tried first, so a
|
|
161
|
+
helper such as __iAtomicExch maps to atomicExch and not to the type
|
|
162
|
+
cast its body calls. The longest such name wins. This maps the
|
|
163
|
+
header helpers onto the API names the docs use.
|
|
164
|
+
"""
|
|
165
|
+
for needles in (
|
|
166
|
+
[name],
|
|
167
|
+
sorted(_reachable_builtins(name, calls)),
|
|
168
|
+
):
|
|
169
|
+
best = ""
|
|
170
|
+
best_len = 0
|
|
171
|
+
for doc_name, (doc_entry, _source) in sorted(documented.items()):
|
|
172
|
+
# A tier-3 name must be a documented function, not a word of
|
|
173
|
+
# an example or a feature table cell.
|
|
174
|
+
signature = str(doc_entry.get("signature") or "")
|
|
175
|
+
if not declares_name(signature, doc_name):
|
|
176
|
+
continue
|
|
177
|
+
token = _stripped(doc_name)
|
|
178
|
+
if len(token) < 4:
|
|
179
|
+
continue
|
|
180
|
+
if any(_starts_at_word(token, needle) for needle in needles) and len(token) > best_len:
|
|
181
|
+
best = doc_name
|
|
182
|
+
best_len = len(token)
|
|
183
|
+
if best:
|
|
184
|
+
return best
|
|
185
|
+
return ""
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _starts_at_word(token: str, name: str) -> bool:
|
|
189
|
+
"""The token matches the name from a word start, underscores aside.
|
|
190
|
+
|
|
191
|
+
Word starts are the name start, the letter after an underscore or
|
|
192
|
+
a digit, and a case hump. Matching skips underscores of the name,
|
|
193
|
+
so a multiword builtin name still matches its word run.
|
|
194
|
+
"""
|
|
195
|
+
n = len(name)
|
|
196
|
+
for pos in range(n):
|
|
197
|
+
if pos:
|
|
198
|
+
prev = name[pos - 1]
|
|
199
|
+
hump = name[pos].isupper() and not prev.isupper()
|
|
200
|
+
if prev != "_" and not prev.isdigit() and not hump:
|
|
201
|
+
continue
|
|
202
|
+
i = pos
|
|
203
|
+
ok = True
|
|
204
|
+
for ch in token:
|
|
205
|
+
while i < n and name[i] == "_":
|
|
206
|
+
i += 1
|
|
207
|
+
# A width run in the name, such as the 32 of match32, does
|
|
208
|
+
# not split the word for the doc name.
|
|
209
|
+
if i < n and name[i].isdigit() and name[i].lower() != ch:
|
|
210
|
+
while i < n and (name[i].isdigit() or name[i] == "_"):
|
|
211
|
+
i += 1
|
|
212
|
+
if i >= n or name[i].lower() != ch:
|
|
213
|
+
ok = False
|
|
214
|
+
break
|
|
215
|
+
i += 1
|
|
216
|
+
if ok:
|
|
217
|
+
return True
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def apply_cuda_docs(
|
|
222
|
+
rows: list[IntrinsicRecord], calls: dict[str, list[str]], docs: dict[str, Any]
|
|
223
|
+
) -> None:
|
|
224
|
+
"""Fill documentation fields of the CUDA rows from the parsed pages.
|
|
225
|
+
|
|
226
|
+
Join tiers per name: an exact doc entry, then the nearest documented
|
|
227
|
+
caller over the header call graph, then a documented name contained
|
|
228
|
+
in the row name or in a builtin it reaches. The chosen entry stores
|
|
229
|
+
its caller in metadata["doc_via"] on the fallback tiers. A name with
|
|
230
|
+
no entry keeps no URL and keeps the header signature.
|
|
231
|
+
"""
|
|
232
|
+
ulp = docs.get("ulp") or {}
|
|
233
|
+
documented: dict[str, tuple[dict, str]] = {}
|
|
234
|
+
for mapping, source in (
|
|
235
|
+
(docs.get("math") or {}, "cuda-math-api"),
|
|
236
|
+
(docs.get("guide") or {}, "cuda-guide"),
|
|
237
|
+
(docs.get("guide_archive") or {}, "cuda-guide-archive"),
|
|
238
|
+
(docs.get("libdevice") or {}, "libdevice"),
|
|
239
|
+
):
|
|
240
|
+
for doc_name, entry in mapping.items():
|
|
241
|
+
if doc_name not in documented:
|
|
242
|
+
documented[doc_name] = (entry, source)
|
|
243
|
+
for row in rows:
|
|
244
|
+
name = row.name
|
|
245
|
+
via = ""
|
|
246
|
+
hit = documented.get(name)
|
|
247
|
+
if hit is None:
|
|
248
|
+
via = _nearest_documented_caller(name, calls, set(documented))
|
|
249
|
+
if via:
|
|
250
|
+
hit = documented[via]
|
|
251
|
+
if hit is None:
|
|
252
|
+
via = _contained_doc_name(name, calls, documented)
|
|
253
|
+
if via:
|
|
254
|
+
hit = documented[via]
|
|
255
|
+
if hit is None:
|
|
256
|
+
row.url = ""
|
|
257
|
+
continue
|
|
258
|
+
entry, source = hit
|
|
259
|
+
anchor = str(entry.get("anchor") or "")
|
|
260
|
+
page = str(entry.get("page") or "")
|
|
261
|
+
row.url = resolve_url(page, "#" + anchor) if anchor else resolve_url(page)
|
|
262
|
+
if entry.get("signature"):
|
|
263
|
+
row.signature = str(entry["signature"])
|
|
264
|
+
description = str(entry.get("description") or "")
|
|
265
|
+
summary = str(entry.get("summary") or "")
|
|
266
|
+
row.description = _first_sentence(summary or description)
|
|
267
|
+
sections: dict[str, str] = {}
|
|
268
|
+
if description:
|
|
269
|
+
sections["Description"] = description
|
|
270
|
+
parameters = str(entry.get("parameters") or "")
|
|
271
|
+
if not parameters:
|
|
272
|
+
parameters = _signature_params(str(entry.get("signature") or ""))
|
|
273
|
+
if parameters:
|
|
274
|
+
sections["Parameters"] = parameters
|
|
275
|
+
if entry.get("return_value"):
|
|
276
|
+
sections["Return"] = str(entry["return_value"])
|
|
277
|
+
ulp_entry = ulp.get(name) or {}
|
|
278
|
+
accuracy = str(ulp_entry.get("accuracy") or "")
|
|
279
|
+
if not accuracy:
|
|
280
|
+
accuracy = str(entry.get("accuracy") or "")
|
|
281
|
+
if accuracy:
|
|
282
|
+
readable = tex_to_text(accuracy)
|
|
283
|
+
sections["Accuracy"] = readable
|
|
284
|
+
# The web renders the original TeX later; keep it verbatim.
|
|
285
|
+
if readable != accuracy:
|
|
286
|
+
row.metadata["doc_math"] = accuracy
|
|
287
|
+
row.doc_sections = sections
|
|
288
|
+
if entry.get("accuracy"):
|
|
289
|
+
row.notes = [str(entry["accuracy"])]
|
|
290
|
+
row.metadata["doc_source"] = source
|
|
291
|
+
if via:
|
|
292
|
+
row.metadata["doc_via"] = via
|
|
293
|
+
if entry.get("return_value"):
|
|
294
|
+
row.metadata["result"] = str(entry["return_value"])
|