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.
- {simdref-0.0.17/src/simdref.egg-info → simdref-0.0.19}/PKG-INFO +3 -2
- {simdref-0.0.17 → simdref-0.0.19}/README.md +2 -1
- {simdref-0.0.17 → simdref-0.0.19}/pyproject.toml +1 -1
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/annotate.py +138 -14
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/asm_identity.py +35 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/asmline.py +132 -12
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/catkey.py +36 -2
- simdref-0.0.19/src/simdref/cuda.py +50 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/display.py +13 -2
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/filters.py +1 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/ingest_catalog.py +7 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/ingest_sources.py +22 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/lsp.py +150 -18
- {simdref-0.0.17 → simdref-0.0.19/src/simdref.egg-info}/PKG-INFO +3 -2
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref.egg-info/SOURCES.txt +10 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_asm_wire.py +73 -4
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_catkey.py +2 -0
- simdref-0.0.19/tests/test_cuda_declaration_parser.py +60 -0
- simdref-0.0.19/tests/test_cuda_intrinsics.py +109 -0
- simdref-0.0.19/tests/test_cuda_td_expansion.py +108 -0
- simdref-0.0.19/tests/test_cuda_wiring.py +81 -0
- simdref-0.0.19/tests/test_issue_65_branch_target.py +91 -0
- simdref-0.0.19/tests/test_issue_65_foreign_arch.py +236 -0
- simdref-0.0.19/tests/test_issue_65_m5.py +22 -0
- simdref-0.0.19/tests/test_issue_65_sme_za.py +128 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_arm_form_pick.py +3 -3
- simdref-0.0.19/tests/test_lsp_cuda.py +119 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_lsp_ptx.py +19 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_lsp_stdio.py +234 -18
- {simdref-0.0.17 → simdref-0.0.19}/LICENSE +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/setup.cfg +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/__init__.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/__main__.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/arm_instructions.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/cli.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/export.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/htmltext.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/ingest.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/ingest_pdf.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/manpages.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/models.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/operands.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/pdfparse/__init__.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/pdfparse/base.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/pdfparse/intel.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/pdfparse/registry.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/pdfparse/types.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/pdfrefs.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/__init__.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/amd_swopt.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/arm_swopt.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/cores.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/instlatx64.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/intel_guide.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/intel_optref.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/llvm_mca.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/perf_sources/merge.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/ptx.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/queries.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/regtable.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/riscv.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/search.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/storage.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/tablecols.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/tui.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref/ui_labels.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref.egg-info/dependency_links.txt +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref.egg-info/entry_points.txt +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref.egg-info/requires.txt +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/src/simdref.egg-info/top_level.txt +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_a1_sdm_parser.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_a2_perf_rule.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_a4_arm_pdfrefs.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_a5_offline_aarchmrs.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_amd_match_llvm_mc.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_annotate.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_annotate_positions.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_annotate_scans.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_arrangement_tail.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_asm_identity.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_asmline.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_audit_coverage.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_auto_update_on_version_change.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_b1_latency_pairs.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_b2_metadata.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_b3_sdm_forms.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_b4_descriptions_provenance.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_b5_linking.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_b5_perf_family.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_b5_sme.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_c0_perf_priority.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_cli_bare_query.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_cli_bootstrap_progress.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_cli_help.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_cli_llm.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_coverage_live.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_coverage_parity.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_display.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_display_scanners.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_export.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_export_viz.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_fetch_retry.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_filters.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_fixui_display.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_htmltext.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_ingest_enc_fields.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_ingest_sources.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_int_arm_isa_label.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_int_core_ids.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_int_ingest_registration.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_int_sdm_family_fallback.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_intel_operation_and_url.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_isa_detect_llvm_mc.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue2_fixes.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_51.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_52.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_53_54.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_55.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_56_57a.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_57_cpi.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_60_c1.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_60_contract.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_60_crossarch.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_60_matcher.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_65_arm_alias.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_65_compressed.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_65_identity.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_65_m1.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_65_perfdata.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_65_text.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issue_65_timing.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issues_23_24_25.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_issues_51_57_join.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_arm_postindex.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_att_movx.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_autoupdate.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_catalog_gaps.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_ingest_isa_merge.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_intel_size.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_perf_join.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_leftover_riscv_shared.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_lsp_web.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_models.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_no_catalog_row.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_objdump_listings.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_operands.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_pdfparse.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_perf.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_perf_source_amd_swopt.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_perf_source_arm_swopt.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_perf_source_instlatx64.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_perf_source_intel_guide.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_perf_source_intel_optref.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_perf_sources.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_presentation.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_preset_persistence.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_ptx.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_regtable.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_release_tag_pin.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_riscv_docs_pages.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_search.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_search_pushdown.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_shared_source_helpers.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_source_kind_filter.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_source_validation.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_storage_alnum.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_storage_payload.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_storage_schema.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_tablecols.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_tui.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_ui_labels_parity.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_update_build_stamp.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_vendor_riscv_categories.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_vendor_riscv_sources.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_version_matches_pyproject.py +0 -0
- {simdref-0.0.17 → simdref-0.0.19}/tests/test_x86_key_widths.py +0 -0
- {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.
|
|
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 |
|
|
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 |
|
|
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.
|
|
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
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
@@ -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]"
|
|
204
|
-
|
|
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
|