offtarget 0.1.0__py3-none-any.whl

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 (886) hide show
  1. offload/__init__.py +10 -0
  2. offload/__main__.py +5 -0
  3. offload/_bundle.py +74 -0
  4. offload/_root/catalog/acc/claims.yaml +197 -0
  5. offload/_root/catalog/acc/clauses.yaml +391 -0
  6. offload/_root/catalog/acc/no_oracle.yaml +89 -0
  7. offload/_root/catalog/acc/out_of_scope.yaml +17 -0
  8. offload/_root/catalog/acc/rules.yaml +433 -0
  9. offload/_root/catalog/acc/semantic_atoms.yaml +27 -0
  10. offload/_root/catalog/acc/skeleton.yaml +1567 -0
  11. offload/_root/catalog/acc/spec_overrides.yaml +72 -0
  12. offload/_root/catalog/later_forms.yaml +76 -0
  13. offload/_root/catalog/machines.yaml +218 -0
  14. offload/_root/catalog/omp/claims.yaml +2047 -0
  15. offload/_root/catalog/omp/clauses.yaml +914 -0
  16. offload/_root/catalog/omp/derivations.yaml +177 -0
  17. offload/_root/catalog/omp/memory_modes.yaml +40 -0
  18. offload/_root/catalog/omp/no_oracle.yaml +373 -0
  19. offload/_root/catalog/omp/omp50_delta.yaml +377 -0
  20. offload/_root/catalog/omp/out_of_scope.yaml +64 -0
  21. offload/_root/catalog/omp/port_equivalence.yaml +352 -0
  22. offload/_root/catalog/omp/rules.yaml +3131 -0
  23. offload/_root/catalog/omp/rules_coverage.yaml +9939 -0
  24. offload/_root/catalog/omp/semantic_atoms.yaml +34 -0
  25. offload/_root/catalog/omp/skeleton.yaml +9181 -0
  26. offload/_root/catalog/omp/spec_overrides.yaml +932 -0
  27. offload/_root/catalog/toolchains.yaml +472 -0
  28. offload/_root/catalog/vendor_claims_generic.yaml +40925 -0
  29. offload/_root/data/canuse/b.json +1 -0
  30. offload/_root/data/canuse/c.json +1 -0
  31. offload/_root/data/canuse/columns.json +1 -0
  32. offload/_root/data/canuse/d.json +1 -0
  33. offload/_root/data/canuse/f.json +1 -0
  34. offload/_root/data/canuse/index.json +1 -0
  35. offload/_root/data/canuse/p.json +1 -0
  36. offload/_root/data/compilers/afar.json +1 -0
  37. offload/_root/data/compilers/cce19.json +1 -0
  38. offload/_root/data/compilers/cce20.json +1 -0
  39. offload/_root/data/compilers/cce21.json +1 -0
  40. offload/_root/data/compilers/cce21_4.json +1 -0
  41. offload/_root/data/compilers/gcc.json +1 -0
  42. offload/_root/data/compilers/gcc_sm80.json +1 -0
  43. offload/_root/data/compilers/nvhpc.json +1 -0
  44. offload/_root/data/compilers/nvhpc269.json +1 -0
  45. offload/_root/data/findings/AUTHORING.md +107 -0
  46. offload/_root/data/findings/README.md +33 -0
  47. offload/_root/data/findings/afar-c-error-directive-in-target-link.c +37 -0
  48. offload/_root/data/findings/afar-c-lastprivate-distribute-parallel-for-small-type.c +56 -0
  49. offload/_root/data/findings/afar-c-teams-distribute-reduction-first-team.c +59 -0
  50. offload/_root/data/findings/afar-c-update-iterator-copy-mode.c +45 -0
  51. offload/_root/data/findings/afar-c-xnack1-target-is-present.c +31 -0
  52. offload/_root/data/findings/afar-cce19-character-defaultmap.f90 +27 -0
  53. offload/_root/data/findings/afar-cce19-task-in-target-ice.f90 +23 -0
  54. offload/_root/data/findings/afar-clang-requires-device-safesync-hang.c +24 -0
  55. offload/_root/data/findings/afar-flang-firstprivate-map-two-targets.f90 +34 -0
  56. offload/_root/data/findings/afar-flang-loop-bind-teams-reduction.f90 +27 -0
  57. offload/_root/data/findings/afar-flang-task-reduction-max.f90 +31 -0
  58. offload/_root/data/findings/afar-flang-thread-limit-target-parallel-do.f90 +22 -0
  59. offload/_root/data/findings/afar-linear-ref-omp50.f90 +29 -0
  60. offload/_root/data/findings/afar-loop-bind-thread-in-teams.f90 +44 -0
  61. offload/_root/data/findings/afar-nontemporal-assumed-shape-ice.f90 +38 -0
  62. offload/_root/data/findings/afar-udr-derived-type-link.f90 +41 -0
  63. offload/_root/data/findings/afar-xnack1-atomic-min-nested-parallel.f90 +53 -0
  64. offload/_root/data/findings/afar-xnack1-present-not-enforced.f90 +36 -0
  65. offload/_root/data/findings/afar-xnack1-target-is-present.f90 +47 -0
  66. offload/_root/data/findings/apps/afar-cce-teams-private-array-nested-parallel-wrong.f90 +100 -0
  67. offload/_root/data/findings/apps/cce20-device-link-infer-address-spaces-crash.f90 +159 -0
  68. offload/_root/data/findings/apps/cce21-device-link-agpr-copy-mfma-assert.f90 +343 -0
  69. offload/_root/data/findings/apps/cce21-private-array-loaded-subscript-wrong.f90 +113 -0
  70. offload/_root/data/findings/apps/controls/bubble-simple-rddot.f90 +284 -0
  71. offload/_root/data/findings/apps/controls/bubble-two-substeps.f90 +287 -0
  72. offload/_root/data/findings/apps/controls/cbc-inline.f90 +97 -0
  73. offload/_root/data/findings/apps/controls/cbc-integers.f90 +91 -0
  74. offload/_root/data/findings/apps/controls/cbc-only-density.f90 +97 -0
  75. offload/_root/data/findings/apps/controls/cbc-only-velocity.f90 +97 -0
  76. offload/_root/data/findings/apps/controls/gcc-acc-c-tile-control.c +26 -0
  77. offload/_root/data/findings/apps/controls/gcc-acc-tile-control.f90 +29 -0
  78. offload/_root/data/findings/apps/controls/private-array-subscript-probes.f90 +45 -0
  79. offload/_root/data/findings/apps/controls/teams-private-array-read-par.f90 +31 -0
  80. offload/_root/data/findings/apps/controls/teams-private-array-seq.f90 +30 -0
  81. offload/_root/data/findings/apps/controls/teams-private-array-shared.f90 +34 -0
  82. offload/_root/data/findings/apps/controls/teams-private-array-split.f90 +36 -0
  83. offload/_root/data/findings/apps/controls/teams-private-array-write-par.f90 +31 -0
  84. offload/_root/data/findings/apps/gcc-acc-c-tile-partial-tiles-wrong.c +60 -0
  85. offload/_root/data/findings/apps/gcc-acc-tile-partial-tiles-wrong.f90 +64 -0
  86. offload/_root/data/findings/catalog/afar-c-complex-reduction-link.c +66 -0
  87. offload/_root/data/findings/catalog/afar-c-critical-hint-link.c +68 -0
  88. offload/_root/data/findings/catalog/afar-c-ordered-doacross-link.c +62 -0
  89. offload/_root/data/findings/catalog/afar-c-reduction-task-link.c +81 -0
  90. offload/_root/data/findings/catalog/afar-flang-absent-optional-target-crash.f90 +76 -0
  91. offload/_root/data/findings/catalog/afar-flang-critical-target-parallel-wrong.f90 +74 -0
  92. offload/_root/data/findings/catalog/afar-flang-dist-schedule-chunk.f90 +80 -0
  93. offload/_root/data/findings/catalog/afar-flang-linear-parallel-ice.f90 +68 -0
  94. offload/_root/data/findings/catalog/afar-flang-named-critical-link.f90 +67 -0
  95. offload/_root/data/findings/catalog/afar-flang-ordered-wrong.f90 +78 -0
  96. offload/_root/data/findings/catalog/afar-flang-reduction-minus-rejected.f90 +77 -0
  97. offload/_root/data/findings/catalog/afar-flang-reduction-task-ice.f90 +68 -0
  98. offload/_root/data/findings/catalog/afar-flang-sections-in-target-ice.f90 +67 -0
  99. offload/_root/data/findings/catalog/afar-flang-workdistribute-ice.f90 +71 -0
  100. offload/_root/data/findings/catalog/c-allocate-const-mem-firstprivate-wrong.c +79 -0
  101. offload/_root/data/findings/catalog/cce-acc-allocate-in-compute-crash.f90 +65 -0
  102. offload/_root/data/findings/catalog/cce-acc-character-dummy-routine-ice.f90 +81 -0
  103. offload/_root/data/findings/catalog/cce-acc-loop-seq-in-routine-wrong.f90 +81 -0
  104. offload/_root/data/findings/catalog/cce-acc-stop-in-compute-crash.f90 +62 -0
  105. offload/_root/data/findings/catalog/cce-allocators-stack-ice.f90 +81 -0
  106. offload/_root/data/findings/catalog/cce-atomic-default-mem-order-acquire-release-rejected.f90 +61 -0
  107. offload/_root/data/findings/catalog/cce-c-allocate-align-link.c +83 -0
  108. offload/_root/data/findings/catalog/cce-c-allocate-allocator-modifier-rejected.c +77 -0
  109. offload/_root/data/findings/catalog/cce-c-device-type-directive-name-ice.c +70 -0
  110. offload/_root/data/findings/catalog/cce-c-dist-schedule-chunk.c +81 -0
  111. offload/_root/data/findings/catalog/cce-c-f-pointer-device-link.f90 +70 -0
  112. offload/_root/data/findings/catalog/cce-c-lastprivate-parallel-in-seq-loop-wrong.c +64 -0
  113. offload/_root/data/findings/catalog/cce-c-long-double-ice.c +61 -0
  114. offload/_root/data/findings/catalog/cce-c-map-iterator-ice.c +92 -0
  115. offload/_root/data/findings/catalog/cce-c-motion-iterator-rejected.c +68 -0
  116. offload/_root/data/findings/catalog/cce-c-ordered-doacross-link.c +64 -0
  117. offload/_root/data/findings/catalog/cce-c-ordered-link.c +77 -0
  118. offload/_root/data/findings/catalog/cce-c-recursive-function-ice.c +62 -0
  119. offload/_root/data/findings/catalog/cce-c-reduction-inscan-ice.c +67 -0
  120. offload/_root/data/findings/catalog/cce-c-reduction-task-link.c +73 -0
  121. offload/_root/data/findings/catalog/cce-c-sections-in-target-wrong.c +70 -0
  122. offload/_root/data/findings/catalog/cce-c-single-masked-task-link.c +66 -0
  123. offload/_root/data/findings/catalog/cce-c-task-in-target-link.c +75 -0
  124. offload/_root/data/findings/catalog/cce-c-uses-allocators-traits-rejected.c +71 -0
  125. offload/_root/data/findings/catalog/cce-flang-allocate-const-mem-ice.f90 +67 -0
  126. offload/_root/data/findings/catalog/cce-flang-allocate-in-target-crash.f90 +68 -0
  127. offload/_root/data/findings/catalog/cce-flang-lastprivate-parallel-in-seq-loop-wrong.f90 +83 -0
  128. offload/_root/data/findings/catalog/cce-flang-stop-in-target-crash.f90 +64 -0
  129. offload/_root/data/findings/catalog/cce-iterator-link.f90 +67 -0
  130. offload/_root/data/findings/catalog/cce-linear-lastprivate-ice.f90 +69 -0
  131. offload/_root/data/findings/catalog/cce-nohost-link.f90 +90 -0
  132. offload/_root/data/findings/catalog/cce-reduction-inscan-ice.f90 +66 -0
  133. offload/_root/data/findings/catalog/cce-reduction-task-link.f90 +76 -0
  134. offload/_root/data/findings/catalog/cce19-reduction-task-addrspacecast-ice.f90 +77 -0
  135. offload/_root/data/findings/catalog/cce20-c-defaultmap-private-storage-rejected.c +85 -0
  136. offload/_root/data/findings/catalog/cce20-map-self-rejected.f90 +64 -0
  137. offload/_root/data/findings/catalog/cce21-c-declare-target-local-rejected.c +73 -0
  138. offload/_root/data/findings/catalog/cce21-c-target-loop-ice.c +77 -0
  139. offload/_root/data/findings/catalog/defaultmap-present-private-array-abort.f90 +73 -0
  140. offload/_root/data/findings/catalog/nvhpc-acc-associate-illegal-address.f90 +66 -0
  141. offload/_root/data/findings/catalog/nvhpc-c-function-pointer-kernel-missing.c +68 -0
  142. offload/_root/data/findings/catalog/nvhpc-c-long-double-kernel-missing.c +64 -0
  143. offload/_root/data/findings/catalog/nvhpc-c-static-local-kernel-missing.c +73 -0
  144. offload/_root/data/findings/catalog/nvhpc-c-vla-kernel-missing.c +63 -0
  145. offload/_root/data/findings/catalog/nvhpc-flang-type-bound-kernel-missing.f90 +86 -0
  146. offload/_root/data/findings/cce-acc-init-set-runtime-assert.f90 +35 -0
  147. offload/_root/data/findings/cce-acc-reduction-200-int8-scalars-wrong.f90 +1278 -0
  148. offload/_root/data/findings/cce-acc-routine-bind-module-ice.f90 +35 -0
  149. offload/_root/data/findings/cce-acc-self-condition-ignored.f90 +17 -0
  150. offload/_root/data/findings/cce-c-dist-schedule-segv.c +34 -0
  151. offload/_root/data/findings/cce-c-map-delete-refcount.c +47 -0
  152. offload/_root/data/findings/cce-c-map-present-assert.c +45 -0
  153. offload/_root/data/findings/cce-c-openmp45-device-link.c +34 -0
  154. offload/_root/data/findings/cce-c-parallel-for-in-target.c +73 -0
  155. offload/_root/data/findings/cce-defaultmap-tofrom-explicit-map.f90 +92 -0
  156. offload/_root/data/findings/cce-mapper-next-map-copy-back.f90 +48 -0
  157. offload/_root/data/findings/cce-trim-merge-output-one-character.f90 +40 -0
  158. offload/_root/data/findings/cce-uses-allocators-traits-omp50.f90 +28 -0
  159. offload/_root/data/findings/cce19-acc-if-false-link.f90 +17 -0
  160. offload/_root/data/findings/cce19-c-reduction-63-scalars-one-wrong.c +235 -0
  161. offload/_root/data/findings/cce19-target-parallel-guarded-loop.f90 +35 -0
  162. offload/_root/data/findings/cce19-target-parallel-single-ice.f90 +38 -0
  163. offload/_root/data/findings/cce21-alloc-2e32-sum-wrong.f90 +68 -0
  164. offload/_root/data/findings/cce21-defaultmap-aggregate-flag.f90 +52 -0
  165. offload/_root/data/findings/cce21-in-reduction-assumed-shape-write-lost.f90 +61 -0
  166. offload/_root/data/findings/cce21-in-reduction-taskwait-link.f90 +59 -0
  167. offload/_root/data/findings/cce21-linear-modifier-default-none.f90 +59 -0
  168. offload/_root/data/findings/clang-c-declare-target-to-omp52.c +50 -0
  169. offload/_root/data/findings/clang-c-linear-directive-name-hang.c +30 -0
  170. offload/_root/data/findings/clang-c-map-iterator-omp51.c +51 -0
  171. offload/_root/data/findings/clang-c-shared-declare-target-combined.c +58 -0
  172. offload/_root/data/findings/dataenv/cce-value-dummy-target-write-visible.f90 +74 -0
  173. offload/_root/data/findings/defaultmap-none-loop-variable.f90 +69 -0
  174. offload/_root/data/findings/fortran-loop-bind-thread-in-parallel.f90 +53 -0
  175. offload/_root/data/findings/fortran-schedule-static-in-target.f90 +55 -0
  176. offload/_root/data/findings/fresh/cce-common-block-name-in-map-rejected.f90 +47 -0
  177. offload/_root/data/findings/fresh/cce-if-stop-in-target-ice-one-condition.f90 +38 -0
  178. offload/_root/data/findings/fresh/cce-if-stop-in-target-ice.f90 +43 -0
  179. offload/_root/data/findings/fresh/cce-list-directed-print-in-target-copy-crash.f90 +40 -0
  180. offload/_root/data/findings/fresh/cce19-acc-automatic-array-copyout-copy-crash.f90 +56 -0
  181. offload/_root/data/findings/fresh/cce21-combined-if-runtime-expr-loop-skipped.f90 +57 -0
  182. offload/_root/data/findings/fresh/nvhpc-acc-gang-worker-collapse-nested-vector-reduction-lost.f90 +67 -0
  183. offload/_root/data/findings/fresh/nvhpc-teams-loop-module-allocatable-assign-fort2-crash.f90 +73 -0
  184. offload/_root/data/findings/fuzz/1131-afar.f90 +48 -0
  185. offload/_root/data/findings/fuzz/1391-cce20.f90 +40 -0
  186. offload/_root/data/findings/fuzz/14-cce20.f90 +36 -0
  187. offload/_root/data/findings/fuzz/1505-cce20.f90 +88 -0
  188. offload/_root/data/findings/fuzz/1540-afar.f90 +44 -0
  189. offload/_root/data/findings/fuzz/158-afar.f90 +63 -0
  190. offload/_root/data/findings/fuzz/16-cce20.f90 +39 -0
  191. offload/_root/data/findings/fuzz/1644-cce21.f90 +31 -0
  192. offload/_root/data/findings/fuzz/165-cce19.f90 +52 -0
  193. offload/_root/data/findings/fuzz/1749-cce20.f90 +34 -0
  194. offload/_root/data/findings/fuzz/1936-afar.f90 +61 -0
  195. offload/_root/data/findings/fuzz/1978-cce19.f90 +41 -0
  196. offload/_root/data/findings/fuzz/2014-cce19.f90 +46 -0
  197. offload/_root/data/findings/fuzz/2224-cce19.f90 +30 -0
  198. offload/_root/data/findings/fuzz/2334-cce19.f90 +57 -0
  199. offload/_root/data/findings/fuzz/536-cce19.f90 +38 -0
  200. offload/_root/data/findings/fuzz/591-cce19.f90 +54 -0
  201. offload/_root/data/findings/fuzz/650-afar.f90 +58 -0
  202. offload/_root/data/findings/fuzz/698-afar.f90 +68 -0
  203. offload/_root/data/findings/fuzz/700-afar.f90 +46 -0
  204. offload/_root/data/findings/fuzz/775-cce21.f90 +33 -0
  205. offload/_root/data/findings/fuzz/87-afar.f90 +47 -0
  206. offload/_root/data/findings/fuzz/916-afar.f90 +67 -0
  207. offload/_root/data/findings/fuzz/958-cce20.f90 +44 -0
  208. offload/_root/data/findings/fuzz/98-cce19.f90 +40 -0
  209. offload/_root/data/findings/fuzz/README.md +236 -0
  210. offload/_root/data/findings/fuzz/b1-121-afar.f90 +47 -0
  211. offload/_root/data/findings/fuzz/b1-183-afar.f90 +55 -0
  212. offload/_root/data/findings/fuzz/b1-22-cce20.f90 +38 -0
  213. offload/_root/data/findings/fuzz/b1-231-afar.f90 +56 -0
  214. offload/_root/data/findings/fuzz/b1-268-afar.f90 +46 -0
  215. offload/_root/data/findings/fuzz/b2-183-afar.f90 +57 -0
  216. offload/_root/data/findings/fuzz/b2-209-cce19.f90 +51 -0
  217. offload/_root/data/findings/fuzz/b2-213-afar.f90 +63 -0
  218. offload/_root/data/findings/fuzz/b2-52-afar.f90 +56 -0
  219. offload/_root/data/findings/fuzz/b2-68-afar.f90 +54 -0
  220. offload/_root/data/findings/fuzz/b2-85-cce20.f90 +55 -0
  221. offload/_root/data/findings/fuzz/b3-188-cce19.f90 +90 -0
  222. offload/_root/data/findings/fuzz/b3-190-cce21.f90 +88 -0
  223. offload/_root/data/findings/fuzz/b3-197-cce19.f90 +77 -0
  224. offload/_root/data/findings/fuzz/b3-250-cce20.f90 +89 -0
  225. offload/_root/data/findings/fuzz/b3-278-cce19.f90 +70 -0
  226. offload/_root/data/findings/fuzz/b3-45-cce21.f90 +83 -0
  227. offload/_root/data/findings/fuzz/b3-54-afar.f90 +67 -0
  228. offload/_root/data/findings/fuzz/b3-61-cce20.f90 +100 -0
  229. offload/_root/data/findings/fuzz/b3-9-afar.f90 +100 -0
  230. offload/_root/data/findings/fuzz/b3-9-cce21.f90 +103 -0
  231. offload/_root/data/findings/fuzz/b3-94-afar.f90 +80 -0
  232. offload/_root/data/findings/fuzz/b4-39-afar.f90 +141 -0
  233. offload/_root/data/findings/fuzz/b4-86-cce19.f90 +71 -0
  234. offload/_root/data/findings/fuzz/controls/gcc-complex-pointer-store-control.f90 +28 -0
  235. offload/_root/data/findings/fuzz/controls/gcc-declare-reduction-module-control.f90 +29 -0
  236. offload/_root/data/findings/fuzz/controls/gcc-target-simd-map-nontemporal-control.f90 +21 -0
  237. offload/_root/data/findings/fuzz/controls/nvhpc-allocatable-component-mapped-control.f90 +22 -0
  238. offload/_root/data/findings/fuzz/controls/nvhpc-allocatable-component-unreferenced-control.f90 +22 -0
  239. offload/_root/data/findings/fuzz/controls/nvhpc-default-none-removed-control.f90 +18 -0
  240. offload/_root/data/findings/fuzz/controls/nvhpc-default-shared-control.f90 +18 -0
  241. offload/_root/data/findings/fuzz/controls/nvhpc-defaultmap-firstprivate-removed-control.f90 +15 -0
  242. offload/_root/data/findings/fuzz/controls/nvhpc-defaultmap-to-scalar-reduction-mapped-control.f90 +17 -0
  243. offload/_root/data/findings/fuzz/controls/nvhpc-defaultmap-to-scalar-removed-control.f90 +17 -0
  244. offload/_root/data/findings/fuzz/controls/nvhpc-defaultmap-tofrom-lastprivate-control.f90 +15 -0
  245. offload/_root/data/findings/fuzz/controls/nvhpc-depend-explicit-map-control.f90 +22 -0
  246. offload/_root/data/findings/fuzz/controls/nvhpc-depend-removed-control.f90 +22 -0
  247. offload/_root/data/findings/fuzz/controls/nvhpc-distribute-end-distribute-control.f90 +16 -0
  248. offload/_root/data/findings/fuzz/controls/nvhpc-distribute-parallel-num-threads-removed-control.f90 +26 -0
  249. offload/_root/data/findings/fuzz/controls/nvhpc-distribute-parallel-removed-control.f90 +22 -0
  250. offload/_root/data/findings/fuzz/controls/nvhpc-distribute-simd-no-end-directive-control.f90 +15 -0
  251. offload/_root/data/findings/fuzz/controls/nvhpc-explicit-length-character-dummy-control.f90 +18 -0
  252. offload/_root/data/findings/fuzz/controls/nvhpc-loop-character-array-value-control.f90 +23 -0
  253. offload/_root/data/findings/fuzz/controls/nvhpc-loop-character-distribute-control.f90 +20 -0
  254. offload/_root/data/findings/fuzz/controls/nvhpc-map-always-first-control.f90 +15 -0
  255. offload/_root/data/findings/fuzz/controls/nvhpc-map-always-removed-control.f90 +15 -0
  256. offload/_root/data/findings/fuzz/controls/nvhpc-num-threads-removed-control.f90 +12 -0
  257. offload/_root/data/findings/fuzz/controls/nvhpc-target-parallel-firstprivate-dropped-control.f90 +15 -0
  258. offload/_root/data/findings/fuzz/controls/nvhpc-target-parallel-single-nowait-control.f90 +13 -0
  259. offload/_root/data/findings/fuzz/controls/nvhpc-target-parallel-split-control.f90 +15 -0
  260. offload/_root/data/findings/fuzz/controls/nvhpc-target-simd-collapse-split-control.f90 +21 -0
  261. offload/_root/data/findings/fuzz/controls/nvhpc-target-simd-end-directive-control.f90 +14 -0
  262. offload/_root/data/findings/fuzz/controls/nvhpc-target-simd-no-collapse-control.f90 +19 -0
  263. offload/_root/data/findings/fuzz/controls/nvhpc-target-simd-split-no-end-directive-control.f90 +15 -0
  264. offload/_root/data/findings/fuzz/gcc-declare-reduction-module-not-found.f90 +66 -0
  265. offload/_root/data/findings/fuzz/gcc-strided-target-update-rejected.f90 +58 -0
  266. offload/_root/data/findings/fuzz/gcc-target-simd-map-nontemporal-rejected.f90 +54 -0
  267. offload/_root/data/findings/fuzz/nvhpc-allocatable-component-implicit-map-illegal-address.f90 +84 -0
  268. offload/_root/data/findings/fuzz/nvhpc-assumed-length-character-dummy-map-ice.f90 +69 -0
  269. offload/_root/data/findings/fuzz/nvhpc-default-none-reduction-lastprivate-fort1-crash.f90 +70 -0
  270. offload/_root/data/findings/fuzz/nvhpc-defaultmap-firstprivate-lastprivate-ice.f90 +71 -0
  271. offload/_root/data/findings/fuzz/nvhpc-defaultmap-to-scalar-reduction-lost.f90 +82 -0
  272. offload/_root/data/findings/fuzz/nvhpc-depend-implicit-map-illegal-address.f90 +81 -0
  273. offload/_root/data/findings/fuzz/nvhpc-distribute-parallel-num-threads-writes-lost.f90 +79 -0
  274. offload/_root/data/findings/fuzz/nvhpc-end-distribute-simd-rejected.f90 +70 -0
  275. offload/_root/data/findings/fuzz/nvhpc-loop-character-achar-unsupported.f90 +75 -0
  276. offload/_root/data/findings/fuzz/nvhpc-map-always-after-map-to-not-copied-back.f90 +70 -0
  277. offload/_root/data/findings/fuzz/nvhpc-num-threads-reduction-identity-lost.f90 +54 -0
  278. offload/_root/data/findings/fuzz/nvhpc-target-parallel-firstprivate-translate-failure.f90 +66 -0
  279. offload/_root/data/findings/fuzz/nvhpc-target-parallel-single-hang.f90 +68 -0
  280. offload/_root/data/findings/fuzz/nvhpc-target-simd-collapse-translate-failure.f90 +73 -0
  281. offload/_root/data/findings/fuzz/nvhpc-target-simd-no-end-directive-rejected.f90 +68 -0
  282. offload/_root/data/findings/fuzz/par-s1-2082-gcc_g90.f90 +71 -0
  283. offload/_root/data/findings/fuzz/s1-2370-cce21.f90 +67 -0
  284. offload/_root/data/findings/fuzz/s1-2592-cce21.f90 +56 -0
  285. offload/_root/data/findings/fuzz/s1-3463-cce21.f90 +92 -0
  286. offload/_root/data/findings/fuzz/s1-4339-afar.f90 +79 -0
  287. offload/_root/data/findings/fuzz/s1-6085-cce21.f90 +96 -0
  288. offload/_root/data/findings/fuzz/s1-6760-cce20.f90 +65 -0
  289. offload/_root/data/findings/fuzz/s1-6888-cce21.f90 +76 -0
  290. offload/_root/data/findings/fuzz/s1-7882-cce20.f90 +63 -0
  291. offload/_root/data/findings/fuzz/s1-8634-cce21.f90 +45 -0
  292. offload/_root/data/findings/fuzz/s1-91-cce21.f90 +92 -0
  293. offload/_root/data/findings/fuzz/s1-9443-afar.f90 +59 -0
  294. offload/_root/data/findings/fuzz/s1-9914-cce19.f90 +78 -0
  295. offload/_root/data/findings/gcc/controls/gcc-acc-c-detach-control.c +34 -0
  296. offload/_root/data/findings/gcc/controls/gcc-acc-detach-control.f90 +26 -0
  297. offload/_root/data/findings/gcc/controls/gcc-defaultmap-to-pointer-control.f90 +31 -0
  298. offload/_root/data/findings/gcc/controls/gcc-target-in-task-control.c +26 -0
  299. offload/_root/data/findings/gcc/controls/gcc-target-in-task-control.f90 +21 -0
  300. offload/_root/data/findings/gcc/gcc-acc-bind-device-type-rejected.f90 +71 -0
  301. offload/_root/data/findings/gcc/gcc-acc-c-bind-device-type-rejected.c +76 -0
  302. offload/_root/data/findings/gcc/gcc-acc-c-detach-delete-attach-underflow.c +74 -0
  303. offload/_root/data/findings/gcc/gcc-acc-c-gang-dim-rejected.c +79 -0
  304. offload/_root/data/findings/gcc/gcc-acc-c-link-in-routine-rejected.c +82 -0
  305. offload/_root/data/findings/gcc/gcc-acc-detach-delete-attach-underflow.f90 +66 -0
  306. offload/_root/data/findings/gcc/gcc-acc-gang-dim-collapse-force-rejected.f90 +69 -0
  307. offload/_root/data/findings/gcc/gcc-acc-init-set-shutdown-rejected.f90 +70 -0
  308. offload/_root/data/findings/gcc/gcc-acc-link-in-routine-rejected.f90 +82 -0
  309. offload/_root/data/findings/gcc/gcc-acc-use-device-allocatable-rejected.f90 +88 -0
  310. offload/_root/data/findings/gcc/gcc-c-motion-mapper-rejected.c +110 -0
  311. offload/_root/data/findings/gcc/gcc-c-omp60-undocumented-rejected.c +76 -0
  312. offload/_root/data/findings/gcc/gcc-c-omp60-unimplemented-rejected.c +119 -0
  313. offload/_root/data/findings/gcc/gcc-c-target-in-task-mandatory-device-not-found.c +87 -0
  314. offload/_root/data/findings/gcc/gcc-c-uses-allocators-traits-unimplemented.c +85 -0
  315. offload/_root/data/findings/gcc/gcc-declare-mapper-rejected.f90 +85 -0
  316. offload/_root/data/findings/gcc/gcc-defaultmap-to-pointer-copy-back.f90 +66 -0
  317. offload/_root/data/findings/gcc/gcc-loop-bind-default-none-rejected.f90 +93 -0
  318. offload/_root/data/findings/gcc/gcc-omp60-undocumented-rejected.f90 +72 -0
  319. offload/_root/data/findings/gcc/gcc-omp60-unimplemented-rejected.f90 +112 -0
  320. offload/_root/data/findings/gcc/gcc-target-in-task-mandatory-device-not-found.f90 +83 -0
  321. offload/_root/data/findings/gcc/gcc-uses-allocators-traits-unimplemented.f90 +78 -0
  322. offload/_root/data/findings/harvest/afar-flang-strided-target-update.f90 +96 -0
  323. offload/_root/data/findings/harvest/cce-acc-declare-create-module-allocatable-xnack1-fault.f90 +58 -0
  324. offload/_root/data/findings/harvest/cce-c-implicit-struct-map-partly-present.c +100 -0
  325. offload/_root/data/findings/harvest/cce-c-strided-target-update.c +74 -0
  326. offload/_root/data/findings/harvest/cce-ftn-target-parallel-thread-query-ice.f90 +67 -0
  327. offload/_root/data/findings/harvest/cce-named-constant-array-declare-target-routine-rejected.f90 +69 -0
  328. offload/_root/data/findings/harvest/cce-use-device-ptr-c-ptr-not-converted.f90 +64 -0
  329. offload/_root/data/findings/harvest/cce21-declare-target-link-module-variable-write-lost.f90 +63 -0
  330. offload/_root/data/findings/harvest/cce21-internal-procedure-calls-internal-in-target-fault.f90 +71 -0
  331. offload/_root/data/findings/harvest/controls/cce-c-implicit-struct-map-partly-present-target-only-control.c +22 -0
  332. offload/_root/data/findings/harvest/controls/cce-c-implicit-struct-map-partly-present-whole-control.c +23 -0
  333. offload/_root/data/findings/harvest/controls/gcc-acc-declare-create-allocatable-control.f90 +34 -0
  334. offload/_root/data/findings/harvest/controls/gcc-c-same-item-two-maps-control.c +17 -0
  335. offload/_root/data/findings/harvest/controls/gcc-c-target-data-struct-pointer-section-control.c +27 -0
  336. offload/_root/data/findings/harvest/controls/gcc-c-target-data-two-maps-control.c +20 -0
  337. offload/_root/data/findings/harvest/controls/gcc-same-item-two-maps-control.f90 +16 -0
  338. offload/_root/data/findings/harvest/controls/gcc-teams-simd-matmul-allocatable-control.f90 +27 -0
  339. offload/_root/data/findings/harvest/gcc-acc-declare-create-allocatable-not-mapped.f90 +66 -0
  340. offload/_root/data/findings/harvest/gcc-c-same-item-two-maps-rejected.c +45 -0
  341. offload/_root/data/findings/harvest/gcc-c-strided-section-rejected.c +70 -0
  342. offload/_root/data/findings/harvest/gcc-c-target-data-struct-pointer-section-ice.c +78 -0
  343. offload/_root/data/findings/harvest/gcc-c-target-data-two-maps-ice.c +49 -0
  344. offload/_root/data/findings/harvest/gcc-same-item-two-maps-rejected.f90 +46 -0
  345. offload/_root/data/findings/harvest/gcc-teams-simd-matmul-allocatable-ice.f90 +53 -0
  346. offload/_root/data/findings/harvest/nvhpc-c-target-data-if-false-use-device-ptr-garbage.c +49 -0
  347. offload/_root/data/findings/harvest/nvhpc-named-constant-array-declare-target-routine-rejected.f90 +65 -0
  348. offload/_root/data/findings/harvest/nvhpc-use-device-ptr-c-ptr-not-found.f90 +60 -0
  349. offload/_root/data/findings/harvest/nvhpc2511-target-nowait-in-parallel-for-ice.c +59 -0
  350. offload/_root/data/findings/hecbench/controls/f16-ctl-float-array.c +28 -0
  351. offload/_root/data/findings/hecbench/controls/pc-ctl-builtin-popcount.c +37 -0
  352. offload/_root/data/findings/hecbench/controls/pc-ctl-shift-sum.c +43 -0
  353. offload/_root/data/findings/hecbench/gcc-c-float16-array-in-target-lto-ice.c +65 -0
  354. offload/_root/data/findings/hecbench/gcc-c-swar-popcount-assembler-error.c +80 -0
  355. offload/_root/data/findings/index.yaml +2453 -0
  356. offload/_root/data/findings/malloc/afar-device-malloc-in-kernel-corrupts-records.c +371 -0
  357. offload/_root/data/findings/malloc/controls/afar-first-use-malloc-direct.c +52 -0
  358. offload/_root/data/findings/malloc/controls/afar-first-use-malloc-fence.c +57 -0
  359. offload/_root/data/findings/malloc/controls/afar-first-use-malloc-flush.c +57 -0
  360. offload/_root/data/findings/malloc/controls/afar-first-use-malloc-stack.c +52 -0
  361. offload/_root/data/findings/multithread-allocatable-section-maps.f90 +91 -0
  362. offload/_root/data/findings/nested/afar-cce-c-call-result-member-in-target-rejected.c +69 -0
  363. offload/_root/data/findings/nested/afar-cce-c-update-struct-and-member-rejected.c +92 -0
  364. offload/_root/data/findings/nested/afar-flang-pointer-root-allocatable-component-enter-data-fault.f90 +121 -0
  365. offload/_root/data/findings/nested/cce-c-struct-and-nested-pointee-sections-ice.c +79 -0
  366. offload/_root/data/findings/nested/cce-fortran-nested-allocatable-component-copy-fault.f90 +64 -0
  367. offload/_root/data/findings/nested/cce-fortran-pointer-to-type-with-allocatable-scalar-component-typed-transfer.f90 +93 -0
  368. offload/_root/data/findings/nested/cce-fortran-update-to-pointer-root-allocatable-scalar-component-no-accelerator-object.f90 +80 -0
  369. offload/_root/data/findings/nested/controls/c3-ctl-nested-data.c +27 -0
  370. offload/_root/data/findings/nested/controls/c5-acc-serial-struct-and-array.c +26 -0
  371. offload/_root/data/findings/nested/controls/e2-ctl-two-directives.c +42 -0
  372. offload/_root/data/findings/nested/controls/e3-ctl-attach-clause.c +42 -0
  373. offload/_root/data/findings/nested/controls/f3-pointer-array-only.c +33 -0
  374. offload/_root/data/findings/nested/controls/f5-ctl-copyout-exit.c +33 -0
  375. offload/_root/data/findings/nested/controls/f6-ctl-pointer-to-struct-array.c +28 -0
  376. offload/_root/data/findings/nested/controls/g2-ctl-allocatable.f90 +34 -0
  377. offload/_root/data/findings/nested/controls/g2-ctl-entered-to.f90 +34 -0
  378. offload/_root/data/findings/nested/controls/g3-ctl-from-struct-no-element.c +27 -0
  379. offload/_root/data/findings/nested/controls/g3-ptrs-two-elements-to.c +27 -0
  380. offload/_root/data/findings/nested/controls/g3-ptrs-two-elements-tofrom.c +27 -0
  381. offload/_root/data/findings/nested/controls/g5-ctl-element-pointers-only.f90 +36 -0
  382. offload/_root/data/findings/nested/controls/g5-ctl-root-pointer-only.f90 +36 -0
  383. offload/_root/data/findings/nested/controls/gp2-ctl-no-mapper-explicit-maps.c +24 -0
  384. offload/_root/data/findings/nested/controls/gt2-ctl-no-always.c +26 -0
  385. offload/_root/data/findings/nested/controls/gt3-ctl-always-on-structure-only.c +26 -0
  386. offload/_root/data/findings/nested/controls/j1-embedded-struct-pointer-to-struct.c +24 -0
  387. offload/_root/data/findings/nested/controls/j2-ctl-pointer-instead-of-embedded.c +24 -0
  388. offload/_root/data/findings/nested/controls/j6-ctl-struct-and-one-pointee-level.c +23 -0
  389. offload/_root/data/findings/nested/controls/j7-ctl-struct-and-embedded-pointee.c +24 -0
  390. offload/_root/data/findings/nested/controls/m2-ctl-variable-root.f90 +32 -0
  391. offload/_root/data/findings/nested/controls/m3-ctl-fixed-array-component.f90 +32 -0
  392. offload/_root/data/findings/nested/controls/m5-ctl-no-enter-data.f90 +33 -0
  393. offload/_root/data/findings/nested/controls/n8-ctl-fixed-array-in-component.f90 +33 -0
  394. offload/_root/data/findings/nested/controls/n9-ctl-allocatable-in-the-variable.f90 +33 -0
  395. offload/_root/data/findings/nested/controls/ns4-ctl-two-directives-struct-then-pointee.c +27 -0
  396. offload/_root/data/findings/nested/controls/ns8-ctl-enter-together-exit-split.c +26 -0
  397. offload/_root/data/findings/nested/controls/nu3-ctl-update-pointee-only.c +28 -0
  398. offload/_root/data/findings/nested/controls/p2-ctl-pointer-variable.c +27 -0
  399. offload/_root/data/findings/nested/controls/p3-ctl-direct-member.c +26 -0
  400. offload/_root/data/findings/nested/controls/q2-ctl-fixed-array.f90 +37 -0
  401. offload/_root/data/findings/nested/controls/q3-ctl-allocatable-component-of-the-variable.f90 +38 -0
  402. offload/_root/data/findings/nested/controls/ta2-ctl-no-allocatable-component.f90 +42 -0
  403. offload/_root/data/findings/nested/controls/ta3-ctl-allocatable-scalar-in-the-variable.f90 +38 -0
  404. offload/_root/data/findings/nested/controls/ta4-ctl-pointer-component-only.f90 +42 -0
  405. offload/_root/data/findings/nested/controls/u3-ctl-update-member-only.c +24 -0
  406. offload/_root/data/findings/nested/controls/u4-ctl-two-updates.c +25 -0
  407. offload/_root/data/findings/nested/controls/u5-ctl-same-list-in-map.c +25 -0
  408. offload/_root/data/findings/nested/controls/ua2-ctl-variable-root.f90 +37 -0
  409. offload/_root/data/findings/nested/controls/ua4-ctl-to-in-place-of-alloc-and-update.f90 +37 -0
  410. offload/_root/data/findings/nested/controls/v2-ctl-parent-first.c +25 -0
  411. offload/_root/data/findings/nested/controls/v4-ctl-embedded-member.c +24 -0
  412. offload/_root/data/findings/nested/gcc-acc-c-exit-data-of-a-pointer-array-element-target-hangs.c +83 -0
  413. offload/_root/data/findings/nested/gcc-acc-c-parent-and-shared-pointee-one-directive.c +88 -0
  414. offload/_root/data/findings/nested/gcc-acc-c-struct-and-member-array-one-directive.c +84 -0
  415. offload/_root/data/findings/nested/gcc-c-always-on-pointer-member-section-ice.c +75 -0
  416. offload/_root/data/findings/nested/gcc-c-declare-mapper-pointer-member-section-gpu-fault.c +93 -0
  417. offload/_root/data/findings/nested/gcc-c-from-struct-and-pointee-section-rejected.c +83 -0
  418. offload/_root/data/findings/nested/gcc-fortran-pointer-component-alloc-bounds-lost.f90 +62 -0
  419. offload/_root/data/findings/nested/gcc-fortran-root-and-element-pointer-components-ice.f90 +77 -0
  420. offload/_root/data/findings/nested/nvhpc-c-pointee-before-parent-in-one-map-list-copy-fault.c +55 -0
  421. offload/_root/data/findings/nested/nvhpc-c-update-from-structure-overwrites-attached-pointer-segv.c +86 -0
  422. offload/_root/data/findings/nvhpc-acc-atomic-update-sum-fort2-crash.f90 +53 -0
  423. offload/_root/data/findings/nvhpc-c-acc-atomic-update-sum-ice.c +43 -0
  424. offload/_root/data/findings/nvhpc-c-dist-schedule-chunk-compile-hang.c +41 -0
  425. offload/_root/data/findings/nvhpc-c-map-present-ignored.c +42 -0
  426. offload/_root/data/findings/nvhpc-c-ordered-in-target-nvcpfe-crash.c +92 -0
  427. offload/_root/data/findings/nvhpc-c-parallel-for-firstprivate-in-target.c +62 -0
  428. offload/_root/data/findings/nvhpc-c-parallel-if-in-target-nvvm-ice.c +56 -0
  429. offload/_root/data/findings/nvhpc-defaultmap-firstprivate-explicit-map.f90 +45 -0
  430. offload/_root/data/findings/nvhpc-defaultmap-from-allocatable-indexing.f90 +60 -0
  431. offload/_root/data/findings/nvhpc-depend-inoutset-ignored.f90 +64 -0
  432. offload/_root/data/findings/nvhpc-dist-schedule-chunk-illegal-address.f90 +52 -0
  433. offload/_root/data/findings/nvhpc-managed-acc-shutdown-host-access.f90 +42 -0
  434. offload/_root/data/findings/nvhpc-parallel-if-in-target-nvvm-ice.f90 +60 -0
  435. offload/_root/data/findings/oop/afar-final-dealloc-device-link.f90 +85 -0
  436. offload/_root/data/findings/oop/afar-pdt-length-parameter-not-implemented.f90 +65 -0
  437. offload/_root/data/findings/oop/cce-class-is-device-link.f90 +86 -0
  438. offload/_root/data/findings/oop/cce-copy-alloc-component-section-fault.f90 +73 -0
  439. offload/_root/data/findings/oop/cce-copy-parent-to-component-tofrom-lost.f90 +65 -0
  440. offload/_root/data/findings/oop/cce-copy-second-member-section-lost.f90 +65 -0
  441. offload/_root/data/findings/oop/cce-element-component-map-lost.f90 +64 -0
  442. offload/_root/data/findings/oop/cce-final-dealloc-device-abort.f90 +85 -0
  443. offload/_root/data/findings/oop/cce-pdt-default-mapper-rejected.f90 +71 -0
  444. offload/_root/data/findings/oop/cce-polymorphic-dispatch-device-link.f90 +100 -0
  445. offload/_root/data/findings/oop/cce-section-of-element-component-overlap.f90 +76 -0
  446. offload/_root/data/findings/oop/cce-select-type-guard-not-taken.f90 +91 -0
  447. offload/_root/data/findings/oop/cce-udr-derived-type-unsupported.f90 +69 -0
  448. offload/_root/data/findings/oop/cce21-element-component-map-lost.f90 +62 -0
  449. offload/_root/data/findings/oop/controls/alloccomp-comp-only.f90 +44 -0
  450. offload/_root/data/findings/oop/controls/alloccomp-whole-explicit-component.f90 +50 -0
  451. offload/_root/data/findings/oop/controls/alloccomp-whole.f90 +49 -0
  452. offload/_root/data/findings/oop/controls/elemsec-element.f90 +47 -0
  453. offload/_root/data/findings/oop/controls/final-local.f90 +64 -0
  454. offload/_root/data/findings/oop/controls/host-class-star-select-type.f90 +58 -0
  455. offload/_root/data/findings/oop/controls/host-firstprivate-defined-assignment.f90 +54 -0
  456. offload/_root/data/findings/oop/controls/host-lastprivate-defined-assignment.f90 +58 -0
  457. offload/_root/data/findings/oop/controls/host-private-finalization.f90 +51 -0
  458. offload/_root/data/findings/oop/controls/host-select-type.f90 +65 -0
  459. offload/_root/data/findings/oop/controls/parent-and-comp-component-only.f90 +45 -0
  460. offload/_root/data/findings/oop/controls/pdt-kind.f90 +46 -0
  461. offload/_root/data/findings/oop/controls/pdt-len-host.f90 +43 -0
  462. offload/_root/data/findings/oop/controls/pdt-len-unparameterized.f90 +46 -0
  463. offload/_root/data/findings/oop/controls/poly-dispatch-static.f90 +75 -0
  464. offload/_root/data/findings/oop/controls/section-alloc-whole-component.f90 +49 -0
  465. offload/_root/data/findings/oop/controls/section-element-whole-array.f90 +50 -0
  466. offload/_root/data/findings/oop/controls/section-element-whole-component.f90 +50 -0
  467. offload/_root/data/findings/oop/controls/section-fixed.f90 +43 -0
  468. offload/_root/data/findings/oop/controls/tbp-override.f90 +64 -0
  469. offload/_root/data/findings/oop/gcc-acc-parent-and-member-data-rejected.f90 +63 -0
  470. offload/_root/data/findings/oop/gcc-pdt-length-parameter-abort.f90 +68 -0
  471. offload/_root/data/findings/oop/gcc-polymorphic-dispatch-abort.f90 +97 -0
  472. offload/_root/data/findings/oop/gcc-vtab-not-marked-for-offload.f90 +94 -0
  473. offload/_root/data/findings/oop/nvhpc-copy-alloc-component-illegal-address.f90 +76 -0
  474. offload/_root/data/findings/oop/nvhpc-copy-pdt-length-parameter-illegal-address.f90 +69 -0
  475. offload/_root/data/findings/oop/nvhpc-named-mapper-rejected.f90 +65 -0
  476. offload/_root/data/findings/oop/nvhpc-polymorphic-dispatch-translate-failure.f90 +90 -0
  477. offload/_root/data/findings/oop/nvhpc-polymorphism-runtime-unsupported.f90 +94 -0
  478. offload/_root/data/findings/oop/nvhpc-type-bound-call-rejected.f90 +89 -0
  479. offload/_root/data/findings/oop/nvhpc-udr-derived-type-rejected.f90 +67 -0
  480. offload/_root/data/findings/reviews/afar-c-error-directive-in-target-link.json +217 -0
  481. offload/_root/data/findings/reviews/afar-c-lastprivate-distribute-parallel-for-small-type.json +113 -0
  482. offload/_root/data/findings/reviews/afar-c-teams-distribute-reduction-first-team.json +130 -0
  483. offload/_root/data/findings/reviews/afar-c-update-iterator-copy-mode.json +113 -0
  484. offload/_root/data/findings/reviews/afar-cce19-character-defaultmap.json +292 -0
  485. offload/_root/data/findings/reviews/afar-clang-requires-device-safesync-hang.json +191 -0
  486. offload/_root/data/findings/reviews/afar-flang-firstprivate-map-two-targets.json +130 -0
  487. offload/_root/data/findings/reviews/afar-flang-loop-bind-teams-reduction.json +126 -0
  488. offload/_root/data/findings/reviews/afar-flang-task-reduction-max.json +113 -0
  489. offload/_root/data/findings/reviews/afar-flang-thread-limit-target-parallel-do.json +113 -0
  490. offload/_root/data/findings/reviews/afar-loop-bind-thread-in-teams.json +191 -0
  491. offload/_root/data/findings/reviews/afar-nontemporal-assumed-shape-ice.json +113 -0
  492. offload/_root/data/findings/reviews/afar-udr-derived-type-link.json +113 -0
  493. offload/_root/data/findings/reviews/afar-xnack1-atomic-min-nested-parallel.json +113 -0
  494. offload/_root/data/findings/reviews/afar-xnack1-target-is-present.json +113 -0
  495. offload/_root/data/findings/reviews/apps--afar-cce-teams-private-array-nested-parallel-wrong.json +113 -0
  496. offload/_root/data/findings/reviews/apps--cce20-device-link-infer-address-spaces-crash.json +113 -0
  497. offload/_root/data/findings/reviews/apps--cce21-device-link-agpr-copy-mfma-assert.json +113 -0
  498. offload/_root/data/findings/reviews/apps--cce21-private-array-loaded-subscript-wrong.json +113 -0
  499. offload/_root/data/findings/reviews/apps--gcc-acc-c-tile-partial-tiles-wrong.json +113 -0
  500. offload/_root/data/findings/reviews/catalog--afar-c-ordered-doacross-link.json +113 -0
  501. offload/_root/data/findings/reviews/catalog--afar-c-reduction-task-link.json +113 -0
  502. offload/_root/data/findings/reviews/catalog--afar-flang-absent-optional-target-crash.json +113 -0
  503. offload/_root/data/findings/reviews/catalog--afar-flang-critical-target-parallel-wrong.json +113 -0
  504. offload/_root/data/findings/reviews/catalog--afar-flang-dist-schedule-chunk.json +113 -0
  505. offload/_root/data/findings/reviews/catalog--afar-flang-linear-parallel-ice.json +113 -0
  506. offload/_root/data/findings/reviews/catalog--afar-flang-named-critical-link.json +126 -0
  507. offload/_root/data/findings/reviews/catalog--afar-flang-ordered-wrong.json +113 -0
  508. offload/_root/data/findings/reviews/catalog--afar-flang-reduction-minus-rejected.json +113 -0
  509. offload/_root/data/findings/reviews/catalog--afar-flang-sections-in-target-ice.json +113 -0
  510. offload/_root/data/findings/reviews/catalog--afar-flang-workdistribute-ice.json +113 -0
  511. offload/_root/data/findings/reviews/catalog--c-allocate-const-mem-firstprivate-wrong.json +113 -0
  512. offload/_root/data/findings/reviews/catalog--cce-acc-character-dummy-routine-ice.json +113 -0
  513. offload/_root/data/findings/reviews/catalog--cce-acc-loop-seq-in-routine-wrong.json +113 -0
  514. offload/_root/data/findings/reviews/catalog--cce-allocators-stack-ice.json +113 -0
  515. offload/_root/data/findings/reviews/catalog--cce-c-allocate-align-link.json +113 -0
  516. offload/_root/data/findings/reviews/catalog--cce-c-allocate-allocator-modifier-rejected.json +113 -0
  517. offload/_root/data/findings/reviews/catalog--cce-c-dist-schedule-chunk.json +113 -0
  518. offload/_root/data/findings/reviews/catalog--cce-c-f-pointer-device-link.json +130 -0
  519. offload/_root/data/findings/reviews/catalog--cce-c-long-double-ice.json +217 -0
  520. offload/_root/data/findings/reviews/catalog--cce-c-map-iterator-ice.json +217 -0
  521. offload/_root/data/findings/reviews/catalog--cce-c-recursive-function-ice.json +113 -0
  522. offload/_root/data/findings/reviews/catalog--cce-c-sections-in-target-wrong.json +113 -0
  523. offload/_root/data/findings/reviews/catalog--cce-flang-allocate-in-target-crash.json +113 -0
  524. offload/_root/data/findings/reviews/catalog--cce-flang-lastprivate-parallel-in-seq-loop-wrong.json +113 -0
  525. offload/_root/data/findings/reviews/catalog--cce-flang-stop-in-target-crash.json +113 -0
  526. offload/_root/data/findings/reviews/catalog--cce-iterator-link.json +113 -0
  527. offload/_root/data/findings/reviews/catalog--cce-linear-lastprivate-ice.json +113 -0
  528. offload/_root/data/findings/reviews/catalog--cce-reduction-inscan-ice.json +113 -0
  529. offload/_root/data/findings/reviews/catalog--cce-reduction-task-link.json +113 -0
  530. offload/_root/data/findings/reviews/catalog--cce19-reduction-task-addrspacecast-ice.json +113 -0
  531. offload/_root/data/findings/reviews/catalog--cce20-c-defaultmap-private-storage-rejected.json +217 -0
  532. offload/_root/data/findings/reviews/catalog--cce20-map-self-rejected.json +204 -0
  533. offload/_root/data/findings/reviews/catalog--cce21-c-target-loop-ice.json +126 -0
  534. offload/_root/data/findings/reviews/catalog--defaultmap-present-private-array-abort.json +236 -0
  535. offload/_root/data/findings/reviews/catalog--nvhpc-acc-associate-illegal-address.json +113 -0
  536. offload/_root/data/findings/reviews/catalog--nvhpc-c-function-pointer-kernel-missing.json +268 -0
  537. offload/_root/data/findings/reviews/cce-acc-init-set-runtime-assert.json +113 -0
  538. offload/_root/data/findings/reviews/cce-acc-reduction-200-int8-scalars-wrong.json +113 -0
  539. offload/_root/data/findings/reviews/cce-acc-routine-bind-module-ice.json +113 -0
  540. offload/_root/data/findings/reviews/cce-c-dist-schedule-segv.json +113 -0
  541. offload/_root/data/findings/reviews/cce-c-map-delete-refcount.json +149 -0
  542. offload/_root/data/findings/reviews/cce-c-map-present-assert.json +126 -0
  543. offload/_root/data/findings/reviews/cce-c-openmp45-device-link.json +113 -0
  544. offload/_root/data/findings/reviews/cce-c-parallel-for-in-target.json +141 -0
  545. offload/_root/data/findings/reviews/cce-mapper-next-map-copy-back.json +208 -0
  546. offload/_root/data/findings/reviews/cce-uses-allocators-traits-omp50.json +191 -0
  547. offload/_root/data/findings/reviews/cce19-c-reduction-63-scalars-one-wrong.json +113 -0
  548. offload/_root/data/findings/reviews/cce19-target-parallel-guarded-loop.json +113 -0
  549. offload/_root/data/findings/reviews/cce19-target-parallel-single-ice.json +113 -0
  550. offload/_root/data/findings/reviews/cce21-alloc-2e32-sum-wrong.json +113 -0
  551. offload/_root/data/findings/reviews/cce21-defaultmap-aggregate-flag.json +210 -0
  552. offload/_root/data/findings/reviews/cce21-in-reduction-assumed-shape-write-lost.json +113 -0
  553. offload/_root/data/findings/reviews/cce21-in-reduction-taskwait-link.json +113 -0
  554. offload/_root/data/findings/reviews/clang-c-declare-target-to-omp52.json +113 -0
  555. offload/_root/data/findings/reviews/clang-c-linear-directive-name-hang.json +113 -0
  556. offload/_root/data/findings/reviews/clang-c-shared-declare-target-combined.json +132 -0
  557. offload/_root/data/findings/reviews/dataenv--cce-value-dummy-target-write-visible.json +113 -0
  558. offload/_root/data/findings/reviews/defaultmap-none-loop-variable.json +113 -0
  559. offload/_root/data/findings/reviews/fortran-schedule-static-in-target.json +113 -0
  560. offload/_root/data/findings/reviews/fresh--cce-common-block-name-in-map-rejected.json +113 -0
  561. offload/_root/data/findings/reviews/fresh--cce-list-directed-print-in-target-copy-crash.json +217 -0
  562. offload/_root/data/findings/reviews/fresh--cce19-acc-automatic-array-copyout-copy-crash.json +113 -0
  563. offload/_root/data/findings/reviews/fresh--cce21-combined-if-runtime-expr-loop-skipped.json +113 -0
  564. offload/_root/data/findings/reviews/fresh--nvhpc-acc-gang-worker-collapse-nested-vector-reduction-lost.json +113 -0
  565. offload/_root/data/findings/reviews/fresh--nvhpc-teams-loop-module-allocatable-assign-fort2-crash.json +113 -0
  566. offload/_root/data/findings/reviews/fuzz--1505-cce20.json +113 -0
  567. offload/_root/data/findings/reviews/fuzz--1540-afar.json +113 -0
  568. offload/_root/data/findings/reviews/fuzz--1978-cce19.json +130 -0
  569. offload/_root/data/findings/reviews/fuzz--2334-cce19.json +113 -0
  570. offload/_root/data/findings/reviews/fuzz--591-cce19.json +147 -0
  571. offload/_root/data/findings/reviews/fuzz--650-afar.json +113 -0
  572. offload/_root/data/findings/reviews/fuzz--916-afar.json +130 -0
  573. offload/_root/data/findings/reviews/fuzz--98-cce19.json +113 -0
  574. offload/_root/data/findings/reviews/fuzz--b1-121-afar.json +160 -0
  575. offload/_root/data/findings/reviews/fuzz--b2-209-cce19.json +141 -0
  576. offload/_root/data/findings/reviews/fuzz--b2-68-afar.json +113 -0
  577. offload/_root/data/findings/reviews/fuzz--b2-85-cce20.json +217 -0
  578. offload/_root/data/findings/reviews/fuzz--b3-188-cce19.json +113 -0
  579. offload/_root/data/findings/reviews/fuzz--b3-278-cce19.json +212 -0
  580. offload/_root/data/findings/reviews/fuzz--b3-54-afar.json +113 -0
  581. offload/_root/data/findings/reviews/fuzz--b3-61-cce20.json +191 -0
  582. offload/_root/data/findings/reviews/fuzz--b4-86-cce19.json +113 -0
  583. offload/_root/data/findings/reviews/fuzz--gcc-declare-reduction-module-not-found.json +113 -0
  584. offload/_root/data/findings/reviews/fuzz--gcc-target-simd-map-nontemporal-rejected.json +113 -0
  585. offload/_root/data/findings/reviews/fuzz--nvhpc-allocatable-component-implicit-map-illegal-address.json +141 -0
  586. offload/_root/data/findings/reviews/fuzz--nvhpc-assumed-length-character-dummy-map-ice.json +113 -0
  587. offload/_root/data/findings/reviews/fuzz--nvhpc-default-none-reduction-lastprivate-fort1-crash.json +113 -0
  588. offload/_root/data/findings/reviews/fuzz--nvhpc-defaultmap-firstprivate-lastprivate-ice.json +113 -0
  589. offload/_root/data/findings/reviews/fuzz--nvhpc-defaultmap-to-scalar-reduction-lost.json +113 -0
  590. offload/_root/data/findings/reviews/fuzz--nvhpc-depend-implicit-map-illegal-address.json +113 -0
  591. offload/_root/data/findings/reviews/fuzz--nvhpc-distribute-parallel-num-threads-writes-lost.json +113 -0
  592. offload/_root/data/findings/reviews/fuzz--nvhpc-end-distribute-simd-rejected.json +113 -0
  593. offload/_root/data/findings/reviews/fuzz--nvhpc-loop-character-achar-unsupported.json +113 -0
  594. offload/_root/data/findings/reviews/fuzz--nvhpc-map-always-after-map-to-not-copied-back.json +113 -0
  595. offload/_root/data/findings/reviews/fuzz--nvhpc-num-threads-reduction-identity-lost.json +113 -0
  596. offload/_root/data/findings/reviews/fuzz--nvhpc-target-parallel-firstprivate-translate-failure.json +191 -0
  597. offload/_root/data/findings/reviews/fuzz--nvhpc-target-parallel-single-hang.json +113 -0
  598. offload/_root/data/findings/reviews/fuzz--nvhpc-target-simd-collapse-translate-failure.json +113 -0
  599. offload/_root/data/findings/reviews/fuzz--nvhpc-target-simd-no-end-directive-rejected.json +113 -0
  600. offload/_root/data/findings/reviews/fuzz--par-s1-2082-gcc_g90.json +126 -0
  601. offload/_root/data/findings/reviews/fuzz--s1-2370-cce21.json +113 -0
  602. offload/_root/data/findings/reviews/fuzz--s1-2592-cce21.json +113 -0
  603. offload/_root/data/findings/reviews/fuzz--s1-3463-cce21.json +113 -0
  604. offload/_root/data/findings/reviews/fuzz--s1-6760-cce20.json +130 -0
  605. offload/_root/data/findings/reviews/fuzz--s1-6888-cce21.json +113 -0
  606. offload/_root/data/findings/reviews/fuzz--s1-7882-cce20.json +113 -0
  607. offload/_root/data/findings/reviews/fuzz--s1-8634-cce21.json +113 -0
  608. offload/_root/data/findings/reviews/fuzz--s1-91-cce21.json +113 -0
  609. offload/_root/data/findings/reviews/fuzz--s1-9443-afar.json +113 -0
  610. offload/_root/data/findings/reviews/fuzz--s1-9914-cce19.json +141 -0
  611. offload/_root/data/findings/reviews/gcc--gcc-acc-bind-device-type-rejected.json +113 -0
  612. offload/_root/data/findings/reviews/gcc--gcc-acc-detach-delete-attach-underflow.json +314 -0
  613. offload/_root/data/findings/reviews/gcc--gcc-acc-gang-dim-collapse-force-rejected.json +113 -0
  614. offload/_root/data/findings/reviews/gcc--gcc-acc-init-set-shutdown-rejected.json +113 -0
  615. offload/_root/data/findings/reviews/gcc--gcc-acc-link-in-routine-rejected.json +113 -0
  616. offload/_root/data/findings/reviews/gcc--gcc-acc-use-device-allocatable-rejected.json +113 -0
  617. offload/_root/data/findings/reviews/gcc--gcc-c-motion-mapper-rejected.json +113 -0
  618. offload/_root/data/findings/reviews/gcc--gcc-c-omp60-undocumented-rejected.json +217 -0
  619. offload/_root/data/findings/reviews/gcc--gcc-c-omp60-unimplemented-rejected.json +236 -0
  620. offload/_root/data/findings/reviews/gcc--gcc-c-target-in-task-mandatory-device-not-found.json +236 -0
  621. offload/_root/data/findings/reviews/gcc--gcc-c-uses-allocators-traits-unimplemented.json +113 -0
  622. offload/_root/data/findings/reviews/gcc--gcc-declare-mapper-rejected.json +113 -0
  623. offload/_root/data/findings/reviews/gcc--gcc-defaultmap-to-pointer-copy-back.json +217 -0
  624. offload/_root/data/findings/reviews/gcc--gcc-loop-bind-default-none-rejected.json +113 -0
  625. offload/_root/data/findings/reviews/harvest--afar-flang-strided-target-update.json +141 -0
  626. offload/_root/data/findings/reviews/harvest--cce-acc-declare-create-module-allocatable-xnack1-fault.json +113 -0
  627. offload/_root/data/findings/reviews/harvest--cce-c-implicit-struct-map-partly-present.json +217 -0
  628. offload/_root/data/findings/reviews/harvest--cce-c-strided-target-update.json +141 -0
  629. offload/_root/data/findings/reviews/harvest--cce-ftn-target-parallel-thread-query-ice.json +130 -0
  630. offload/_root/data/findings/reviews/harvest--cce-named-constant-array-declare-target-routine-rejected.json +113 -0
  631. offload/_root/data/findings/reviews/harvest--cce-use-device-ptr-c-ptr-not-converted.json +113 -0
  632. offload/_root/data/findings/reviews/harvest--cce21-declare-target-link-module-variable-write-lost.json +113 -0
  633. offload/_root/data/findings/reviews/harvest--cce21-internal-procedure-calls-internal-in-target-fault.json +158 -0
  634. offload/_root/data/findings/reviews/harvest--gcc-acc-declare-create-allocatable-not-mapped.json +204 -0
  635. offload/_root/data/findings/reviews/harvest--gcc-c-same-item-two-maps-rejected.json +143 -0
  636. offload/_root/data/findings/reviews/harvest--gcc-c-strided-section-rejected.json +164 -0
  637. offload/_root/data/findings/reviews/harvest--gcc-c-target-data-struct-pointer-section-ice.json +113 -0
  638. offload/_root/data/findings/reviews/harvest--gcc-c-target-data-two-maps-ice.json +113 -0
  639. offload/_root/data/findings/reviews/harvest--gcc-teams-simd-matmul-allocatable-ice.json +113 -0
  640. offload/_root/data/findings/reviews/harvest--nvhpc-c-target-data-if-false-use-device-ptr-garbage.json +113 -0
  641. offload/_root/data/findings/reviews/harvest--nvhpc-named-constant-array-declare-target-routine-rejected.json +126 -0
  642. offload/_root/data/findings/reviews/harvest--nvhpc-use-device-ptr-c-ptr-not-found.json +113 -0
  643. offload/_root/data/findings/reviews/harvest--nvhpc2511-target-nowait-in-parallel-for-ice.json +130 -0
  644. offload/_root/data/findings/reviews/hecbench--gcc-c-float16-array-in-target-lto-ice.json +217 -0
  645. offload/_root/data/findings/reviews/hecbench--gcc-c-swar-popcount-assembler-error.json +126 -0
  646. offload/_root/data/findings/reviews/malloc--afar-device-malloc-in-kernel-corrupts-records.json +270 -0
  647. offload/_root/data/findings/reviews/multithread-allocatable-section-maps.json +191 -0
  648. offload/_root/data/findings/reviews/nested--afar-cce-c-call-result-member-in-target-rejected.json +113 -0
  649. offload/_root/data/findings/reviews/nested--afar-cce-c-update-struct-and-member-rejected.json +126 -0
  650. offload/_root/data/findings/reviews/nested--afar-flang-pointer-root-allocatable-component-enter-data-fault.json +191 -0
  651. offload/_root/data/findings/reviews/nested--cce-c-struct-and-nested-pointee-sections-ice.json +113 -0
  652. offload/_root/data/findings/reviews/nested--cce-fortran-nested-allocatable-component-copy-fault.json +214 -0
  653. offload/_root/data/findings/reviews/nested--cce-fortran-pointer-to-type-with-allocatable-scalar-component-typed-transfer.json +191 -0
  654. offload/_root/data/findings/reviews/nested--cce-fortran-update-to-pointer-root-allocatable-scalar-component-no-accelerator-object.json +297 -0
  655. offload/_root/data/findings/reviews/nested--gcc-acc-c-exit-data-of-a-pointer-array-element-target-hangs.json +204 -0
  656. offload/_root/data/findings/reviews/nested--gcc-acc-c-parent-and-shared-pointee-one-directive.json +130 -0
  657. offload/_root/data/findings/reviews/nested--gcc-acc-c-struct-and-member-array-one-directive.json +141 -0
  658. offload/_root/data/findings/reviews/nested--gcc-c-always-on-pointer-member-section-ice.json +113 -0
  659. offload/_root/data/findings/reviews/nested--gcc-c-declare-mapper-pointer-member-section-gpu-fault.json +113 -0
  660. offload/_root/data/findings/reviews/nested--gcc-c-from-struct-and-pointee-section-rejected.json +113 -0
  661. offload/_root/data/findings/reviews/nested--gcc-fortran-pointer-component-alloc-bounds-lost.json +217 -0
  662. offload/_root/data/findings/reviews/nested--gcc-fortran-root-and-element-pointer-components-ice.json +217 -0
  663. offload/_root/data/findings/reviews/nested--nvhpc-c-pointee-before-parent-in-one-map-list-copy-fault.json +204 -0
  664. offload/_root/data/findings/reviews/nested--nvhpc-c-update-from-structure-overwrites-attached-pointer-segv.json +191 -0
  665. offload/_root/data/findings/reviews/nvhpc-c-acc-atomic-update-sum-ice.json +277 -0
  666. offload/_root/data/findings/reviews/nvhpc-c-dist-schedule-chunk-compile-hang.json +113 -0
  667. offload/_root/data/findings/reviews/nvhpc-c-map-present-ignored.json +113 -0
  668. offload/_root/data/findings/reviews/nvhpc-c-ordered-in-target-nvcpfe-crash.json +113 -0
  669. offload/_root/data/findings/reviews/nvhpc-c-parallel-for-firstprivate-in-target.json +113 -0
  670. offload/_root/data/findings/reviews/nvhpc-defaultmap-firstprivate-explicit-map.json +113 -0
  671. offload/_root/data/findings/reviews/nvhpc-defaultmap-from-allocatable-indexing.json +132 -0
  672. offload/_root/data/findings/reviews/nvhpc-depend-inoutset-ignored.json +113 -0
  673. offload/_root/data/findings/reviews/nvhpc-dist-schedule-chunk-illegal-address.json +113 -0
  674. offload/_root/data/findings/reviews/nvhpc-managed-acc-shutdown-host-access.json +113 -0
  675. offload/_root/data/findings/reviews/nvhpc-parallel-if-in-target-nvvm-ice.json +113 -0
  676. offload/_root/data/findings/reviews/oop--afar-final-dealloc-device-link.json +132 -0
  677. offload/_root/data/findings/reviews/oop--afar-pdt-length-parameter-not-implemented.json +113 -0
  678. offload/_root/data/findings/reviews/oop--cce-class-is-device-link.json +130 -0
  679. offload/_root/data/findings/reviews/oop--cce-copy-alloc-component-section-fault.json +217 -0
  680. offload/_root/data/findings/reviews/oop--cce-copy-parent-to-component-tofrom-lost.json +217 -0
  681. offload/_root/data/findings/reviews/oop--cce-copy-second-member-section-lost.json +113 -0
  682. offload/_root/data/findings/reviews/oop--cce-element-component-map-lost.json +113 -0
  683. offload/_root/data/findings/reviews/oop--cce-final-dealloc-device-abort.json +208 -0
  684. offload/_root/data/findings/reviews/oop--cce-pdt-default-mapper-rejected.json +191 -0
  685. offload/_root/data/findings/reviews/oop--cce-polymorphic-dispatch-device-link.json +247 -0
  686. offload/_root/data/findings/reviews/oop--cce-section-of-element-component-overlap.json +113 -0
  687. offload/_root/data/findings/reviews/oop--cce-select-type-guard-not-taken.json +191 -0
  688. offload/_root/data/findings/reviews/oop--cce-udr-derived-type-unsupported.json +132 -0
  689. offload/_root/data/findings/reviews/oop--gcc-acc-parent-and-member-data-rejected.json +141 -0
  690. offload/_root/data/findings/reviews/oop--gcc-pdt-length-parameter-abort.json +141 -0
  691. offload/_root/data/findings/reviews/oop--gcc-polymorphic-dispatch-abort.json +217 -0
  692. offload/_root/data/findings/reviews/oop--gcc-vtab-not-marked-for-offload.json +113 -0
  693. offload/_root/data/findings/reviews/oop--nvhpc-copy-alloc-component-illegal-address.json +113 -0
  694. offload/_root/data/findings/reviews/oop--nvhpc-copy-pdt-length-parameter-illegal-address.json +236 -0
  695. offload/_root/data/findings/reviews/oop--nvhpc-named-mapper-rejected.json +113 -0
  696. offload/_root/data/findings/reviews/oop--nvhpc-polymorphic-dispatch-translate-failure.json +223 -0
  697. offload/_root/data/findings/reviews/oop--nvhpc-polymorphism-runtime-unsupported.json +126 -0
  698. offload/_root/data/findings/reviews/oop--nvhpc-type-bound-call-rejected.json +113 -0
  699. offload/_root/data/findings/reviews/oop--nvhpc-udr-derived-type-rejected.json +113 -0
  700. offload/_root/data/findings/reviews/stress--afar-c-lastprivate-combined-target-not-mapped.json +113 -0
  701. offload/_root/data/findings/reviews/stress--afar-target-scalar-implicit-firstprivate.json +175 -0
  702. offload/_root/data/findings/reviews/stress--cce-acc-declare-create-scalar-serial-firstprivate.json +113 -0
  703. offload/_root/data/findings/reviews/stress--cce-c-distribute-lastprivate-lost.json +113 -0
  704. offload/_root/data/findings/reviews/stress--cce-c-flto-offload-dropped.json +204 -0
  705. offload/_root/data/findings/reviews/stress--cce-ftn-cif-record-split-abort.json +113 -0
  706. offload/_root/data/findings/reviews/stress--cce-lastprivate-num-threads-collapse-simd.json +113 -0
  707. offload/_root/data/findings/reviews/stress--cce-teams-reduction-many-teams-wrap.json +113 -0
  708. offload/_root/data/findings/reviews/stress--cce21-declare-target-scalar-map-write-lost.json +113 -0
  709. offload/_root/data/findings/reviews/stress--gcc-declare-target-allocatable-component-ice.json +191 -0
  710. offload/_root/data/findings/reviews/stress--nvhpc-acc-derived-type-component-not-copied.json +126 -0
  711. offload/_root/data/findings/reviews/stress--nvhpc-c-lastprivate-teams-distribute-parallel-for-lost.json +113 -0
  712. offload/_root/data/findings/reviews/stress--nvhpc-declare-target-scalar-write-lost.json +113 -0
  713. offload/_root/data/findings/reviews/stress--nvhpc-lastprivate-combined-target-not-mapped.json +113 -0
  714. offload/_root/data/findings/reviews/stress--nvhpc-target-scalar-implicit-firstprivate.json +130 -0
  715. offload/_root/data/findings/reviews/target-update-sections-in-parallel.json +113 -0
  716. offload/_root/data/findings/stress/afar-c-lastprivate-combined-target-not-mapped.c +72 -0
  717. offload/_root/data/findings/stress/afar-target-scalar-implicit-firstprivate.f90 +47 -0
  718. offload/_root/data/findings/stress/cce-acc-declare-create-scalar-serial-firstprivate.f90 +43 -0
  719. offload/_root/data/findings/stress/cce-c-distribute-lastprivate-lost.c +52 -0
  720. offload/_root/data/findings/stress/cce-c-flto-offload-dropped.c +73 -0
  721. offload/_root/data/findings/stress/cce-ftn-cif-record-split-abort.f90 +109 -0
  722. offload/_root/data/findings/stress/cce-lastprivate-num-threads-collapse-simd.f90 +66 -0
  723. offload/_root/data/findings/stress/cce-teams-reduction-many-teams-wrap.f90 +55 -0
  724. offload/_root/data/findings/stress/cce21-declare-target-scalar-map-write-lost.f90 +58 -0
  725. offload/_root/data/findings/stress/controls/gcc-declare-target-allocatable-component-control.f90 +29 -0
  726. offload/_root/data/findings/stress/gcc-declare-target-allocatable-component-ice.f90 +61 -0
  727. offload/_root/data/findings/stress/nvhpc-acc-derived-type-component-not-copied.f90 +33 -0
  728. offload/_root/data/findings/stress/nvhpc-c-lastprivate-teams-distribute-parallel-for-lost.c +62 -0
  729. offload/_root/data/findings/stress/nvhpc-declare-target-scalar-write-lost.f90 +43 -0
  730. offload/_root/data/findings/stress/nvhpc-lastprivate-combined-target-not-mapped.f90 +60 -0
  731. offload/_root/data/findings/stress/nvhpc-target-scalar-implicit-firstprivate.f90 +41 -0
  732. offload/_root/data/findings/target-update-sections-in-parallel.f90 +53 -0
  733. offload/_root/data/issues.json +1 -0
  734. offload/_root/data/search.json +1 -0
  735. offload/_root/data/vendor/checks/build.py +288 -0
  736. offload/_version.py +24 -0
  737. offload/canuse.py +88 -0
  738. offload/cli.py +52 -0
  739. offload/data.py +85 -0
  740. offload/engine/__init__.py +0 -0
  741. offload/engine/acc_c_gen.py +415 -0
  742. offload/engine/acc_gen.py +1104 -0
  743. offload/engine/acc_rules.py +656 -0
  744. offload/engine/apps.py +804 -0
  745. offload/engine/baseline.py +324 -0
  746. offload/engine/c_gen.py +883 -0
  747. offload/engine/c_program.py +913 -0
  748. offload/engine/c_rules.py +503 -0
  749. offload/engine/campaign.py +1180 -0
  750. offload/engine/campaigns/__init__.py +2 -0
  751. offload/engine/campaigns/hecbench.py +153 -0
  752. offload/engine/campaigns/nested.py +76 -0
  753. offload/engine/campaigns/oop.py +61 -0
  754. offload/engine/canary.py +275 -0
  755. offload/engine/catalog.py +860 -0
  756. offload/engine/citations.py +532 -0
  757. offload/engine/clang_oracle.py +208 -0
  758. offload/engine/combos.py +234 -0
  759. offload/engine/confirm/__init__.py +2 -0
  760. offload/engine/confirm/backlog.py +159 -0
  761. offload/engine/confirm/claims.py +423 -0
  762. offload/engine/confirm/collectors.py +665 -0
  763. offload/engine/confirm/decisions.py +202 -0
  764. offload/engine/confirm/draft.py +195 -0
  765. offload/engine/confirm/engine.py +157 -0
  766. offload/engine/confirm/execute.py +382 -0
  767. offload/engine/confirm/facts.py +467 -0
  768. offload/engine/confirm/findings.py +617 -0
  769. offload/engine/confirm/forms.py +145 -0
  770. offload/engine/confirm/ingest.py +372 -0
  771. offload/engine/confirm/jobs.py +215 -0
  772. offload/engine/confirm/legality.py +323 -0
  773. offload/engine/confirm/machines.py +254 -0
  774. offload/engine/confirm/minimize.py +268 -0
  775. offload/engine/confirm/model.py +249 -0
  776. offload/engine/confirm/ports.py +107 -0
  777. offload/engine/confirm/programs.py +222 -0
  778. offload/engine/confirm/registry.py +151 -0
  779. offload/engine/confirm/regress.py +278 -0
  780. offload/engine/confirm/rules.py +565 -0
  781. offload/engine/confirm/store.py +63 -0
  782. offload/engine/confirm/triage.py +838 -0
  783. offload/engine/confirm/validate.py +607 -0
  784. offload/engine/confirm/witness.py +355 -0
  785. offload/engine/corpus_check.py +388 -0
  786. offload/engine/coverage.py +318 -0
  787. offload/engine/dataenv_gen.py +1318 -0
  788. offload/engine/dataenv_rules.py +278 -0
  789. offload/engine/devtemps.py +146 -0
  790. offload/engine/expected.py +2559 -0
  791. offload/engine/f2c.py +1783 -0
  792. offload/engine/finding_claims.py +330 -0
  793. offload/engine/finding_header.py +229 -0
  794. offload/engine/finding_rows.py +382 -0
  795. offload/engine/flang_oracle.py +299 -0
  796. offload/engine/fsource.py +620 -0
  797. offload/engine/fuzz.py +2455 -0
  798. offload/engine/fuzz_base.py +2016 -0
  799. offload/engine/fuzz_campaign.py +592 -0
  800. offload/engine/fuzz_exec.py +798 -0
  801. offload/engine/fuzz_min.py +1049 -0
  802. offload/engine/fuzz_profile.py +340 -0
  803. offload/engine/fuzz_report.py +1270 -0
  804. offload/engine/fuzz_steer.py +181 -0
  805. offload/engine/gate.py +1013 -0
  806. offload/engine/gate_report.py +711 -0
  807. offload/engine/gate_run.py +118 -0
  808. offload/engine/gate_stored.py +466 -0
  809. offload/engine/gen.py +1114 -0
  810. offload/engine/gen_templates.py +2583 -0
  811. offload/engine/grammar.py +695 -0
  812. offload/engine/harness.py +1472 -0
  813. offload/engine/harvest/__init__.py +3 -0
  814. offload/engine/harvest/c_runtime.py +465 -0
  815. offload/engine/harvest/checks.py +534 -0
  816. offload/engine/harvest/convert.py +784 -0
  817. offload/engine/harvest/f_runtime.py +668 -0
  818. offload/engine/harvest/filters.py +338 -0
  819. offload/engine/harvest/history.py +122 -0
  820. offload/engine/harvest/inventory.py +399 -0
  821. offload/engine/harvest2/__init__.py +5 -0
  822. offload/engine/harvest2/aomp.py +119 -0
  823. offload/engine/harvest2/examples.py +116 -0
  824. offload/engine/harvest2/gomp.py +679 -0
  825. offload/engine/harvest2/hand.py +150 -0
  826. offload/engine/harvest2/split.py +78 -0
  827. offload/engine/harvest_known/__init__.py +2 -0
  828. offload/engine/harvest_known/apps.py +65 -0
  829. offload/engine/harvest_known/cases.py +128 -0
  830. offload/engine/harvest_known/centres.py +184 -0
  831. offload/engine/harvest_known/forum.py +180 -0
  832. offload/engine/harvest_known/relnotes.py +42 -0
  833. offload/engine/harvest_known/textscan.py +107 -0
  834. offload/engine/hdrwrap.py +42 -0
  835. offload/engine/hecbench.py +568 -0
  836. offload/engine/hecbench_report.py +265 -0
  837. offload/engine/hecbench_run.py +483 -0
  838. offload/engine/idioms.py +1427 -0
  839. offload/engine/kbody_cites.py +81 -0
  840. offload/engine/kbody_emit.py +949 -0
  841. offload/engine/kbody_gen.py +795 -0
  842. offload/engine/legality_review.py +236 -0
  843. offload/engine/localcfg.py +58 -0
  844. offload/engine/malloc_dd.py +267 -0
  845. offload/engine/mutate.py +1933 -0
  846. offload/engine/nested_cites.py +196 -0
  847. offload/engine/nested_emit.py +789 -0
  848. offload/engine/nested_gen.py +1721 -0
  849. offload/engine/offload_scope.py +197 -0
  850. offload/engine/ompvv.py +624 -0
  851. offload/engine/oop_gen.py +2249 -0
  852. offload/engine/prerun.py +1435 -0
  853. offload/engine/program_source.py +739 -0
  854. offload/engine/provenance.py +103 -0
  855. offload/engine/reduce.py +2098 -0
  856. offload/engine/report.py +3374 -0
  857. offload/engine/root.py +33 -0
  858. offload/engine/rules.py +4256 -0
  859. offload/engine/run_matrix.py +325 -0
  860. offload/engine/site/__init__.py +1 -0
  861. offload/engine/site/columns.py +53 -0
  862. offload/engine/site/data.py +648 -0
  863. offload/engine/site/header.py +228 -0
  864. offload/engine/site/inputs.py +115 -0
  865. offload/engine/site/matrix.py +434 -0
  866. offload/engine/site/scrub.py +294 -0
  867. offload/engine/skeleton.py +121 -0
  868. offload/engine/spec_text.py +242 -0
  869. offload/engine/stress.py +4122 -0
  870. offload/engine/stress_c.py +1000 -0
  871. offload/engine/stress_exec.py +805 -0
  872. offload/engine/stress_min.py +248 -0
  873. offload/engine/stress_report.py +552 -0
  874. offload/engine/textclean.py +97 -0
  875. offload/engine/toolchains.py +1070 -0
  876. offload/engine/transforms.py +511 -0
  877. offload/engine/usage.py +1252 -0
  878. offload/engine/widen.py +673 -0
  879. offload/engine/widen_progs.py +2396 -0
  880. offload/issues.py +146 -0
  881. offtarget-0.1.0.dist-info/METADATA +86 -0
  882. offtarget-0.1.0.dist-info/RECORD +886 -0
  883. offtarget-0.1.0.dist-info/WHEEL +4 -0
  884. offtarget-0.1.0.dist-info/entry_points.txt +2 -0
  885. offtarget-0.1.0.dist-info/licenses/LICENSE +21 -0
  886. offtarget-0.1.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +20 -0
@@ -0,0 +1,3374 @@
1
+ """Support table and vendor-claims comparison.
2
+
3
+ Reads the matrix (results/matrix.jsonl, one Result per program, toolchain and
4
+ memory mode), the oracle audit summary (tests/data/oracle_audit_summary.json)
5
+ and the vendor claims (catalog/omp/claims.yaml), and renders
6
+ results/support_table.md and results/support_table.csv
7
+ (command line: tools/derive/support_table.py).
8
+ One table row per matrix cell (program x toolchain x HSA_XNACK). The headline
9
+ outcome is HSA_XNACK=1 with THP; the copy-mode column (HSA_XNACK=0) is filled
10
+ where the program ran in copy mode (``COPY_MARK`` marks copy_mode_only). Each cell carries its
11
+ evidence level and its atom's audit disposition. Cells without a separating
12
+ oracle read ``NO_ORACLE_TEXT``, ``LEGALITY_TEXT`` or ``UNVERIFIED_TEXT`` and
13
+ never count as supported. A wrong answer is "confirmed" only when the atom's
14
+ oracle is audited valid. A dagger marks modes that ran on different nodes.
15
+ The omp_vv suite is a second source. OpenACC matrices use the OpenACC catalog."""
16
+ from __future__ import annotations
17
+
18
+ import csv
19
+ import io
20
+ import json
21
+ import re
22
+ from collections import Counter, defaultdict
23
+ from dataclasses import dataclass, field, replace
24
+ from fnmatch import fnmatchcase
25
+ from functools import lru_cache
26
+ from pathlib import Path
27
+
28
+ import yaml
29
+
30
+ from offload.engine.textclean import scrub as _public
31
+ from offload.engine.toolchains import arch_of, base_name, family
32
+ # Report.family (below) is the label category; the toolchain
33
+ # family (message tables: every CCE release is cce19; a gfx90a twin is its
34
+ # compiler) is this one
35
+ from offload.engine.toolchains import family as _tc_family
36
+
37
+ ROOT = Path(__file__).resolve().parent.parent.parent
38
+ MATRIX = ROOT / "results" / "matrix.jsonl"
39
+ BASELINE = ROOT / "results" / "baseline.jsonl"
40
+ AUDIT = ROOT / "tests" / "data" / "oracle_audit_summary.json"
41
+ # The generic claims ingested from the vendor documents
42
+ # (tools/derive/claims.py ingest; mapping in catalog/<model>/claims.yaml)
43
+ CLAIMS = ROOT / "catalog" / "vendor_claims_generic.yaml"
44
+ CLAUSES = ROOT / "catalog" / "omp" / "clauses.yaml"
45
+ RULES = ROOT / "catalog" / "omp" / "rules.yaml"
46
+ OVERRIDES = ROOT / "catalog" / "omp" / "spec_overrides.yaml"
47
+
48
+ # Links in the Markdown are relative to results/support_table.md.
49
+ LINK = {"clauses": "../catalog/omp/clauses.yaml",
50
+ "skeleton": "../catalog/omp/skeleton.yaml",
51
+ "rules": "../catalog/omp/rules.yaml",
52
+ "claims": "../catalog/vendor_claims_generic.yaml",
53
+ "semantic": "../catalog/omp/semantic_atoms.yaml",
54
+ "audit": "../reports/oracle-audit.md"}
55
+ # The OpenACC catalog and audit
56
+ ACC_AUDIT = ROOT / "tests" / "data" / "acc_audit_summary.json"
57
+ ACC_CLAIMS = CLAIMS
58
+ ACC_MATRIX = ROOT / "results" / "acc_matrix.jsonl"
59
+ # The C programs' matrix, audit summary and omp_vv C baseline
60
+ C_MATRIX = ROOT / "results" / "matrix_c.jsonl"
61
+ C_AUDIT = ROOT / "tests" / "data" / "oracle_audit_summary_c.json"
62
+ C_BASELINE = ROOT / "results" / "baseline_c.jsonl"
63
+ # The OpenACC C slice (NVHPC only; its audits are NV_AUDIT's)
64
+ ACC_C_MATRIX = ROOT / "results" / "acc_matrix_c.jsonl"
65
+ ACC_C_AUDIT = ROOT / "tests" / "data" / "acc_audit_summary_c.json"
66
+ LINK_ACC = {k: v.replace("/omp/", "/acc/") for k, v in LINK.items()}
67
+ LINK_ACC["audit"] = "../reports/acc-oracle-audit.md"
68
+ # the OpenACC section of each directive (3.4)
69
+ ACC_DIRECTIVE_SECTION = {
70
+ "parallel": "2.5.1", "serial": "2.5.2", "kernels": "2.5.3",
71
+ "data": "2.6.5", "enter_data": "2.6.6", "exit_data": "2.6.6",
72
+ "host_data": "2.8", "loop": "2.9", "cache": "2.10",
73
+ "parallel_loop": "2.11", "serial_loop": "2.11", "kernels_loop": "2.11",
74
+ "atomic": "2.12", "declare": "2.13", "init": "2.14.1",
75
+ "shutdown": "2.14.2", "set": "2.14.3", "update": "2.14.4",
76
+ "routine": "2.15.1", "wait": "2.16.3"}
77
+
78
+
79
+ def model_of_atom(atom: str) -> str:
80
+ return "acc" if atom.startswith("acc.") else "omp"
81
+
82
+
83
+ def _paths(model: str) -> tuple[Path, Path, Path, dict]:
84
+ """(clauses, rules, overrides, links) of a model's catalog."""
85
+ d = ROOT / "catalog" / model
86
+ return (d / "clauses.yaml", d / "rules.yaml", d / "spec_overrides.yaml",
87
+ LINK_ACC if model == "acc" else LINK)
88
+
89
+
90
+ def table_model(table) -> str:
91
+ """"acc" when every row is an OpenACC atom, else "omp"."""
92
+ return "acc" if table and all(r.atom.startswith("acc.")
93
+ for r in table) else "omp"
94
+
95
+
96
+ def table_lang(table) -> str:
97
+ """"c" when every row is a C program, else "f90"."""
98
+ return "c" if table and all(getattr(r, "lang", "f90") == "c"
99
+ for r in table) else "f90"
100
+
101
+ # "unverified" only where a deterministic oracle is possible and
102
+ # missing; the forms no run can separate read as their category.
103
+ UNVERIFIED_TEXT = "accepted; unverified (an oracle is possible but missing)"
104
+ NO_ORACLE_TEXT = {
105
+ "hint_only": "accepted; conforms vacuously",
106
+ "implementation_defined": "implementation-defined",
107
+ "equivalent_default": "equivalent to the default (no separable effect)"}
108
+ LEGALITY_TEXT = "accepted (its oracle is the illegal variant)"
109
+ NO_ORACLE_NAME = {"hint_only": "hint only",
110
+ "implementation_defined": "implementation-defined",
111
+ "equivalent_default": "equivalent default"}
112
+ EVIDENCE_LEVELS = ("construct", "probe", "copy_mode_only", "hint_only",
113
+ "implementation_defined", "equivalent_default",
114
+ "legality", "unverified")
115
+ # evidence whose accepted run is never credited as support
116
+ UNCREDITED = ("unverified", "legality") + tuple(NO_ORACLE_TEXT)
117
+ # the toolchains and their names come from catalog/toolchains.yaml
118
+ # (offload.engine.toolchains): the AMD columns (each with its HSA_XNACK modes,
119
+ # then its twins), the NVIDIA machine's toolchains get columns of their own
120
+ from offload.engine.toolchains import NVIDIA_TOOLCHAINS # noqa: E402
121
+ from offload.engine.toolchains import ARCH_SUFFIX as _SUFFIX, registry as _reg # noqa: E402
122
+ # the AMD columns: every AMD toolchain with a report column that is neither a
123
+ # twin nor a regression release (AFAR, CCE, GCC)
124
+ ALL_TOOLCHAINS = _reg().of_gpu("amd")
125
+ # the toolchains with rows in the matrix being rendered (build_table narrows
126
+ # it); a column for a compiler that was never run would only say "not run"
127
+ TOOLCHAINS = list(ALL_TOOLCHAINS)
128
+ HEADLINE, COPY = 1, 0
129
+ MODES = (HEADLINE, COPY) # headline HSA_XNACK=1 first, then copy mode
130
+ # The gfx90a twin of each toolchain (Tioga MI250X, a discrete GPU,
131
+ # HSA_XNACK=0 only): a third column per compiler, after its two MI300A
132
+ # modes. G90 holds the twins with rows in the matrix being rendered.
133
+ G90_SUFFIX = _SUFFIX["gfx90a"]
134
+ G90: list[str] = []
135
+ # Every twin with rows in the matrix being rendered, in catalog order: the
136
+ # gfx90a ones and those on other machines (MI300X, MI350X, copy memory only).
137
+ # Each is a cell column after its compiler's MI300A modes; only the gfx90a
138
+ # cell enters the portable verdict (portable3).
139
+ TWINS: list[str] = []
140
+ # the AMD columns' toolchains with MI300A rows of their own (a compiler whose
141
+ # MI300A rows are left out, GCC, has only its twins' columns)
142
+ MI300A: list[str] = []
143
+ # Toolchains of other machines (NVIDIA: nvhpc on wingtip, A100,
144
+ # sm_80) get columns of their own after the AMD ones, one per memory mode
145
+ # of their machine (catalog/machines.yaml: headline mode first); the AMD
146
+ # columns, cells and verdicts are computed as before and never read an
147
+ # NVIDIA row. NV holds those with rows in the matrix being rendered.
148
+ NV: list[str] = []
149
+ # toolchains left out of the support tables on purpose (catalog
150
+ # ``report_column: false``): their rows are dropped, never an error
151
+ REPORT_EXCLUDED = tuple(n for n, t in _reg().toolchains.items()
152
+ if not t.report_column)
153
+ # the column names: every column (and the excluded) by its catalog label
154
+ TC_NAME = {n: _reg().label(n) for n in (*ALL_TOOLCHAINS,
155
+ *(w for t in ALL_TOOLCHAINS for w in _reg().twins_of(t)),
156
+ *NVIDIA_TOOLCHAINS, *REPORT_EXCLUDED)
157
+ if n in _reg().toolchains} # a gfx942 toolchain may have no twin yet
158
+
159
+
160
+ def nv_modes(tc: str) -> tuple[int, ...]:
161
+ """The row keys of an NVIDIA toolchain's modes, headline first (those
162
+ the toolchain has: GCC runs separate memory only)."""
163
+ from offload.engine.confirm.machines import machine_of
164
+ from offload.engine.toolchains import TOOLCHAINS as _TC
165
+ m = machine_of(tc)
166
+ keys = (m.row_key(m.headline_mode),) + tuple(
167
+ m.row_key(x) for x in m.other_modes(m.headline_mode))
168
+ return tuple(k for k in keys if k in _TC[tc].xnack_modes)
169
+
170
+
171
+ def nv_mode_name(tc: str, x: int) -> str:
172
+ """The mode's name for an NVIDIA row ("separate", "managed"): the
173
+ machine's mode, ``copy`` named after NVHPC's -gpu=mem:separate."""
174
+ from offload.engine.confirm.machines import machine_of
175
+ m = machine_of(tc).mode_of_row_key(x)
176
+ return "separate" if m == "copy" else m
177
+
178
+
179
+ def _mode_word(tc: str, x: int) -> str:
180
+ """How a cell's memory mode is named in a label."""
181
+ return nv_mode_name(tc, x) if tc in NVIDIA_TOOLCHAINS \
182
+ else f"HSA_XNACK={x}"
183
+
184
+
185
+ def g90_of(tc: str) -> str:
186
+ """The gfx90a twin's name of a gfx942 toolchain."""
187
+ return f"{tc}{G90_SUFFIX}"
188
+
189
+
190
+ def twins_of(tc: str) -> list[str]:
191
+ """The twins of a compiler with rows in the matrix being rendered."""
192
+ return [w for w in _reg().twins_of(tc) if w in TWINS]
193
+
194
+
195
+ def columns() -> list[tuple[str, int]]:
196
+ """The (toolchain, xnack) cell columns, per compiler: HSA_XNACK=1 and
197
+ 0 on MI300A, then each twin with rows (HSA_XNACK=0)."""
198
+ out = []
199
+ for tc in TOOLCHAINS:
200
+ out += [(tc, x) for x in MODES] if tc in MI300A else []
201
+ out += [(w, COPY) for w in twins_of(tc)]
202
+ for tc in NV:
203
+ out += [(tc, x) for x in nv_modes(tc)]
204
+ return out
205
+ COPY_MARK = "◊"
206
+ NOT_RUN = "not run"
207
+ COPY_NOT_RUN = "copy-mode check not run"
208
+ # outcomes the memory mode cannot change: no copy-mode pointer needed
209
+ _COMPILE_TIME = ("rejected", "harness_error")
210
+ CLAIM_VALUES = ("supported", "unsupported", "partial")
211
+ CLAIM_FORMS = (None, "compile_error", "accepted")
212
+
213
+ # How a rejected legal cell is attributed:
214
+ # - NOT COUNTED (against the atom or in the claims): a listed confound
215
+ # (CONFOUNDS: the message shows that the compiler rejected another feature
216
+ # the program needs), or a diagnostic at an OpenMP directive line that
217
+ # does not carry the atom (_line_confound; e.g. AFAR stops at the
218
+ # uses_allocators line of an allocate program).
219
+ # - COUNTED WITH A CAVEAT (‡): a listed caveat (CAVEATS: the rejection is
220
+ # the atom's own, but the message needs context), or a diagnostic at a
221
+ # non-directive statement that does not carry the atom (the compiler's
222
+ # check of the atom points at a statement that uses a variable).
223
+ # Each entry: (atom patterns, toolchain, message regex, text); it applies
224
+ # only to a rejected cell whose toolchain and reason match.
225
+ _ALLOC = ("the allocate programs also use uses_allocators("
226
+ "omp_default_mem_alloc); AFAR rejects them with 'USES_ALLOCATORS "
227
+ "clause is not implemented yet', so they measure uses_allocators, "
228
+ "not allocate")
229
+ _DSIMD = ("the linear(ref) and linear(uval) programs put the clause on "
230
+ "declare simd; AFAR stops with 'not yet implemented: "
231
+ "omp.declare_simd', so they measure declare simd, not the modifier")
232
+ CONFOUNDS = (
233
+ (("omp.clause.allocate", "omp.clause.allocate.a*"), "afar",
234
+ r"USES_ALLOCATORS", _ALLOC),
235
+ (("omp.clause.linear.ref", "omp.clause.linear.uval"), "afar",
236
+ r"omp\.declare_simd", _DSIMD),
237
+ # 5.0 allows a linear modifier only on declare simd (5.0
238
+ # §2.9.2 p.107), so the 5.0 linear(val) program is a declare simd one
239
+ (("omp.clause.linear.val",), "afar", r"omp\.declare_simd",
240
+ "at 5.0 the linear(val(x)) program puts the clause on declare simd "
241
+ "(5.0 allows no linear modifier on the worksharing-loop and simd "
242
+ "constructs); AFAR stops with 'not yet implemented: omp.declare_simd', "
243
+ "so it measures declare simd, not the modifier"),
244
+ (("omp.clause.allocate",), "cce19",
245
+ r"DYNAMIC_ALLOCATORS",
246
+ "the allocate(t) program "
247
+ "has requires dynamic_allocators (an allocate clause in a target "
248
+ "region without an allocator needs it, 5.2 §6.6 p.179, 6.0 §8.6 "
249
+ "p.313); CCE 19 does not implement that requirement, so it measures "
250
+ "dynamic_allocators, not allocate"),
251
+ (("omp.clause.device_type*", "omp.clause.indirect*"), "cce19",
252
+ r"INDIRECT, LINK, TO or DEVICE_TYPE",
253
+ "CCE 19 rejects the enter spelling on declare target (ftn-1725 "
254
+ "'\"INDIRECT, LINK, TO or DEVICE_TYPE\" was expected'); the 6.0 program "
255
+ "must use enter (to is not allowed on declare target at 6.0), so it "
256
+ "measures enter, not the clause under test"),
257
+ (("omp.directive.target_teams_distribute_simd",), "afar",
258
+ r"DEFAULT clause is not allowed",
259
+ "AFAR rejected the default(none) hygiene clause, which only a "
260
+ "provisional spec override allows on this directive at 5.2"),
261
+ (("omp.clause.default.shared", "omp.clause.default.all"), None,
262
+ r"^ice:",
263
+ "the program needs an explicit task in a target region (default(shared) "
264
+ "differs from the default only on a task); both toolchains fail to "
265
+ "compile any task in a target region, with or without the default "
266
+ "clause (AFAR: segmentation fault in OpenMPIRBuilder::createTask; CCE "
267
+ "19: internal compiler error 'unhandled construct type'), so "
268
+ "it measures the task construct on the device, not "
269
+ "default(shared)"),
270
+ (("omp.clause.uses_allocators.memspace", "omp.clause.uses_allocators.traits"),
271
+ "cce19", r"ftn-1510",
272
+ "CCE 19 rejected the allocator handle my_alloc for lacking a "
273
+ "data-sharing attribute under default(none) (ftn-1510), not the "
274
+ "uses_allocators modifier"),
275
+ )
276
+
277
+
278
+ CAVEATS = (
279
+ (("omp.clause.in_reduction.*",), "afar",
280
+ r"no symbol found for|minus reduction operator is deprecated",
281
+ "AFAR's error is on the companion task_reduction line that the spec "
282
+ "requires for in_reduction (same reduction identifier), not on the "
283
+ "in_reduction clause"),
284
+ (("omp.clause.transparent*",), None, r"(?i)omp_impex",
285
+ "the cause is omp_impex missing from the compiler's omp_lib module (a "
286
+ "6.0 runtime constant the clause needs), not a parse of the clause"),
287
+ (("omp.clause.device_type.nohost",), "cce19", r"f\$gen_mod_",
288
+ "link failure: undefined `f$gen_mod_` from the host build (the host "
289
+ "version of the target region references the nohost procedure); "
290
+ "whether that is a CCE gap or a non-conforming program is open"),
291
+ # Omp_vv cross-check, decided by results/task14/probe runs
292
+ (("omp.clause.order", "omp.clause.order.*"), "afar",
293
+ r"Unhandled clause order in omp\.wsloop",
294
+ "construct-specific: AFAR rejects order on the worksharing-loop "
295
+ "construct this program uses (parallel do; also on loop: 'Unhandled "
296
+ "clause order in omp.loop'), but compiles and runs order(concurrent) "
297
+ "on simd and target simd in both memory modes (probe and "
298
+ "omp_vv test_target_simd_order_concurrent)"),
299
+ (("omp.clause.map.iterator",), "cce19", r"OmpIt\$",
300
+ "construct-specific: CCE 19 fails to link map(iterator) on target "
301
+ "enter data, which this program uses (undefined `OmpIt$k`); on the "
302
+ "target construct it links, runs correctly at HSA_XNACK=0 and fails at "
303
+ "HSA_XNACK=1 in the runtime (hipFree hipErrorInvalidValue; "
304
+ "omp_vv test_target_map_iterators)"),
305
+ )
306
+ VARIABLE_STATEMENT = ("the compiler points at a statement that uses a "
307
+ "variable, not at the clause")
308
+
309
+
310
+ def confound(atom: str, toolchain: str | None = None,
311
+ reason: str | None = None, table=None) -> str | None:
312
+ """The listed text for a rejected cell (atom, toolchain, reason) from
313
+ `table` (CONFOUNDS by default); with only `atom`, any entry for it."""
314
+ for pats, tc, rx, text in (CONFOUNDS if table is None else table):
315
+ if not any(fnmatchcase(atom, p) for p in pats):
316
+ continue
317
+ if toolchain is None or (tc in (None, _tc_family(toolchain))
318
+ and reason is not None
319
+ and re.search(rx, reason)):
320
+ return text
321
+ return None
322
+
323
+
324
+ _LINE_RX = (re.compile(r'\.f90"?:(\d+):\d+'), re.compile(r"Line = (\d+)"),
325
+ # Clang diagnostics of the C programs
326
+ re.compile(r'\.c:(\d+):\d+'),
327
+ # NVHPC, "NVFORTRAN-S-0034-... (<file>.f90: 12)" and
328
+ # nvc's '"<file>.c", line 12: error: ...'
329
+ re.compile(r'\.f90: (\d+)\)'), re.compile(r'\.c", line (\d+):'))
330
+
331
+
332
+ def diagnosed_line(reason: str) -> int | None:
333
+ """The source line a compiler diagnostic in `reason` points at."""
334
+ for rx in _LINE_RX:
335
+ m = rx.search(reason)
336
+ if m:
337
+ return int(m.group(1))
338
+ return None
339
+
340
+
341
+ def _statement(source: str, n: int) -> str | None:
342
+ """Line n (1-based) of `source` with its & continuation lines (C: its
343
+ backslash continuation lines)."""
344
+ lines = source.splitlines()
345
+ if not 1 <= n <= len(lines):
346
+ return None
347
+ cont = "\\" if re.search(r"^\s*#\s*pragma\s+omp", source, re.M) \
348
+ else "&"
349
+ i = j = n - 1
350
+ while i > 0 and lines[i - 1].rstrip().endswith(cont):
351
+ i -= 1
352
+ while j < len(lines) - 1 and lines[j].rstrip().endswith(cont):
353
+ j += 1
354
+ return " ".join(lines[i:j + 1]).lower()
355
+
356
+
357
+ def carries(atom: str, text: str) -> bool:
358
+ """Whether a statement carries the atom: its clause name followed by
359
+ `(` or a word end, or its directive name. A semantic atom
360
+ (catalog/<model>/semantic_atoms.yaml, e.g.
361
+ ``omp.clause.defaultmap.character``) is exercised through the
362
+ program's data, not through a clause, so every statement of its
363
+ program carries it: a rejection at a directive line is the atom's own
364
+ failure."""
365
+ from offload.engine.catalog import semantic_atoms
366
+ if atom in semantic_atoms():
367
+ return True
368
+ parts = atom.split(".")
369
+ if len(parts) >= 3 and parts[1] == "clause":
370
+ return re.search(rf"(?<![\w]){re.escape(parts[2])}\b", text) is not None
371
+ if len(parts) >= 3 and parts[1] == "directive":
372
+ words = parts[2].split("_")
373
+ return re.search(r"\b" + r"[\s_]*".join(map(re.escape, words))
374
+ + r"\b", text) is not None
375
+ return True
376
+
377
+
378
+ def _line_confound(atom: str, reason: str, source: str | None
379
+ ) -> tuple[str, bool]:
380
+ """(text, excluded) for a diagnostic at a line that does not carry the
381
+ atom: excluded when that line is an OpenMP directive, counted with a
382
+ caveat otherwise; ("", False) when the line carries the atom or cannot
383
+ be found."""
384
+ n = diagnosed_line(reason)
385
+ if n is None or source is None:
386
+ return "", False
387
+ text = _statement(source, n)
388
+ if text is None or carries(atom, text):
389
+ return "", False
390
+ if text.lstrip().startswith(("!$omp", "!$acc")) or re.match(
391
+ r"\s*#\s*pragma\s+omp\b", text):
392
+ return (f"the diagnosed directive line {n} does not carry the atom",
393
+ True)
394
+ return f"{VARIABLE_STATEMENT} (line {n})", False
395
+
396
+
397
+ @lru_cache(maxsize=None)
398
+ def _programs(version: int, lang: str = "f90") -> dict:
399
+ from offload.engine.catalog import model_of, slice_atoms
400
+ if lang == "c" and model_of(version) == "acc":
401
+ from offload.engine.acc_c_gen import all_acc_c_programs
402
+ return {p.id: p.source for p in all_acc_c_programs()}
403
+ if lang == "c":
404
+ from offload.engine.c_gen import all_c_programs
405
+ return {p.id: p.source for p in all_c_programs(version)}
406
+ from offload.engine.gen import generate
407
+ return {p.id: p.source for p in generate(slice_atoms(version), version)}
408
+
409
+
410
+ def generated_source(program_id: str, atom: str, version: int) -> str | None:
411
+ """The current generator's source of a matrix program (None if it is no
412
+ longer generated); a C program (``ompNN-c-...``) from
413
+ offload.engine.c_gen."""
414
+ lang = "c" if re.match(r"(omp|acc)\d\d-c-", program_id) else "f90"
415
+ try:
416
+ return _programs(version, lang).get(program_id)
417
+ except Exception:
418
+ return None
419
+
420
+
421
+ def _root_reason(rec: dict) -> str:
422
+ """A CCE reason quoting a module summary (ftn-855/894) is replaced by
423
+ the root error from the stored stderr."""
424
+ reason = rec.get("reason", "")
425
+ if family(rec.get("toolchain", "")) != "cce19" or not re.search(
426
+ r"ftn-(855|894) ftn:", reason):
427
+ return reason
428
+ from offload.engine.harness import classify_message
429
+ found = classify_message("cce19", rec.get("stderr", ""))
430
+ if not found or re.search(r"ftn-(855|894) ftn:", found[1]):
431
+ return reason
432
+ prefix = reason.split(":", 1)[0]
433
+ return f"{prefix}: {found[1]}"
434
+
435
+
436
+ AUDIT_TEXT = {
437
+ "valid": "audited (valid)",
438
+ "unaudited_no_passing_original": "oracle unaudited",
439
+ "hint_only": "no oracle (hint only)",
440
+ "implementation_defined": "no oracle (implementation-defined)",
441
+ "equivalent_default": "no oracle (equivalent default)",
442
+ "legality": "legality (illegal variant)",
443
+ "unverified_oracle_not_written": "unverified (oracle not written)",
444
+ "unverified_needs_other_infrastructure": "unverified (needs other "
445
+ "infrastructure)",
446
+ "not_observable": "not observable",
447
+ "": "not in audit",
448
+ }
449
+
450
+
451
+ # --- the table -----------------------------------------------------------------------
452
+
453
+ @dataclass(frozen=True)
454
+ class Row:
455
+ atom: str
456
+ toolchain: str
457
+ xnack: int
458
+ outcome: str # harness outcome; illegal cells: rejected | not_rejected
459
+ flaky: bool
460
+ version: int = 0
461
+ program_id: str = ""
462
+ legal: bool = True
463
+ violates: str | None = None
464
+ evidence: str = "construct"
465
+ audit: str = "" # audit disposition of (version, atom), "" if none
466
+ label: str = "" # what the report prints for the cell
467
+ reason: str = ""
468
+ needs: tuple = ()
469
+ thp: str = ""
470
+ confound: str = "" # caveat or exclusion reason of a rejection
471
+ excluded: bool = False # rejection not counted (another feature)
472
+ copy_label: str = "" # headline rows: the copy-mode label, or NOT_RUN
473
+ basis: str = "" # no-oracle programs: '<cite>: "<spec sentence>"'
474
+ lang: str = "f90" # "c" for a C program
475
+ # "device" (the atom's headline program) or "host+device" (a
476
+ # host construct feeding a target region, reported apart) and, for the
477
+ # latter, the realistic use its program header names
478
+ scope: str = "device"
479
+ use: str = ""
480
+ # Headline legal rows: the portable verdict (PORTABLE) and,
481
+ # when the two modes differ, the class of the difference (MODE_CLASSES,
482
+ # catalog/mode_differences.yaml; "unclassified" if no entry)
483
+ portable: str = ""
484
+ mode_class: str = ""
485
+ # The outcome of the program's presence checks run at
486
+ # HSA_XNACK=1 (tools/campaigns/presence_x1.py), "" if it has none
487
+ presence_x1: str = ""
488
+ # Headline legal rows: the class of a difference between the
489
+ # MI300A copy-mode cell and the gfx90a cell (ARCH_CLASSES,
490
+ # catalog/mode_differences.yaml arch_entries; "unclassified" if none)
491
+ arch_class: str = ""
492
+ # Failing legal cells: the reproduction gate's status
493
+ # (GATE_STATES, annotate_gate); "" for every other cell
494
+ gate: str = ""
495
+
496
+
497
+ def _audit_map(path: Path | None) -> dict[tuple[int, str], str]:
498
+ if path is None or not Path(path).is_file():
499
+ return {}
500
+ data = json.loads(Path(path).read_text())
501
+ return {(int(v), atom): e.get("disposition", "")
502
+ for v, atoms in data.get("versions", {}).items()
503
+ for atom, e in atoms.items()}
504
+
505
+
506
+ # The NVIDIA toolchains' oracle audits (tools/campaigns/oracle_audit.py
507
+ # --toolchains nvhpc, one run per memory mode), per model and language:
508
+ # {"modes": {"<row key>": <an audit summary>}}
509
+ NV_AUDIT = {("omp", "f90"): ROOT / "tests" / "data" /
510
+ "oracle_audit_summary_nvhpc.json",
511
+ ("omp", "c"): ROOT / "tests" / "data" /
512
+ "oracle_audit_summary_nvhpc_c.json",
513
+ ("acc", "f90"): ROOT / "tests" / "data" /
514
+ "acc_audit_summary_nvhpc.json",
515
+ # The OpenACC C slice
516
+ ("acc", "c"): ROOT / "tests" / "data" /
517
+ "acc_audit_summary_nvhpc_c.json"}
518
+
519
+
520
+ @lru_cache(maxsize=None)
521
+ def _nv_audit_file(path: str) -> dict:
522
+ p = Path(path)
523
+ if not p.is_file():
524
+ return {}
525
+ return {int(x): {(int(v), atom): e.get("disposition", "")
526
+ for v, atoms in doc.get("versions", {}).items()
527
+ for atom, e in atoms.items()}
528
+ for x, doc in json.loads(p.read_text()).get("modes", {}).items()}
529
+
530
+
531
+ def nv_audit_path(toolchain: str, model: str, lang: str) -> Path | None:
532
+ """The NVIDIA audit summary of a toolchain (NV_AUDIT is nvhpc's;
533
+ another release's is the same file name with its toolchain, e.g.
534
+ oracle_audit_summary_nvhpc269.json); a release without its own audit
535
+ reuses nvhpc's (ruling: the programs are unchanged)."""
536
+ path = NV_AUDIT.get((model, lang))
537
+ if path is None or toolchain == "nvhpc":
538
+ return path
539
+ own = path.with_name(path.name.replace("_nvhpc", f"_{toolchain}"))
540
+ return own if own.is_file() else path
541
+
542
+
543
+ def _nv_audit(rec: dict) -> dict:
544
+ path = nv_audit_path(rec["toolchain"], rec.get("model", "omp"),
545
+ "c" if rec.get("lang") == "c" else "f90")
546
+ return _nv_audit_file(str(path)).get(int(rec["xnack"]), {}) \
547
+ if path else {}
548
+
549
+
550
+ def _is_crash(rec: dict | None) -> bool:
551
+ return (rec is not None and rec["outcome"] == "wrong_answer"
552
+ and rec.get("reason", "").startswith("crash:"))
553
+
554
+
555
+ def _crash_kind(reason: str) -> str:
556
+ return reason[len("crash:"):].split(";")[0].strip()
557
+
558
+
559
+ def _label(rec: dict, other: dict | None, disposition: str) -> str:
560
+ """The report text for one legal or illegal cell (see module docstring)."""
561
+ o = rec["outcome"]
562
+ if not rec.get("legal", True):
563
+ return {"rejected": "rejected",
564
+ "harness_error": "harness error"}.get(o, "not rejected")
565
+ if o == "harness_error":
566
+ return "harness error"
567
+ ev = rec.get("evidence", "construct")
568
+ unaudited = disposition.startswith("unaudited")
569
+ mark = " (oracle unaudited)" if unaudited else ""
570
+ flaky = " [flaky]" if rec.get("flaky") else ""
571
+ nv = rec["toolchain"] in NVIDIA_TOOLCHAINS
572
+ if ("zero_copy" in rec.get("needs", ()) and (rec["xnack"] == 0 or nv)
573
+ and o in ("wrong_answer", "fell_back_to_host")):
574
+ how = _crash_kind(rec["reason"]) if _is_crash(rec) else rec["reason"]
575
+ # No NVHPC mode on wingtip is unified memory
576
+ need = (f"{nv_mode_name(rec['toolchain'], rec['xnack'])} memory, no "
577
+ "unified memory on this machine" if nv else "HSA_XNACK=1")
578
+ return ("fails in copy mode: needs zero-copy memory "
579
+ f"({need}); {how.split(';')[0]}{flaky}")
580
+ if _is_crash(rec):
581
+ text = f"crash: {_crash_kind(rec['reason'])}"
582
+ if other is not None and not _is_crash(other):
583
+ text += f", {_mode_word(rec['toolchain'], rec['xnack'])} only"
584
+ return text + mark + flaky
585
+ if o == "rejected":
586
+ return "rejected" + flaky
587
+ if o == "accepted_ignored":
588
+ return "accepted, ignored (warning)" + flaky
589
+ if o == "fell_back_to_host":
590
+ return "fell back to host" + mark + flaky
591
+ if o == "wrong_answer":
592
+ if ev == "probe" and "compile-time" in rec["reason"]:
593
+ return ("compile-time probe failed" + (
594
+ " (confirmed)" if disposition == "valid" else "")
595
+ + mark + flaky)
596
+ if ev == "probe" and "no_error_termination" in rec["reason"]:
597
+ return ("no error termination (negative probe" + (
598
+ ", confirmed" if disposition == "valid" else "")
599
+ + ")" + mark + flaky)
600
+ if unaudited:
601
+ return "wrong answer (oracle unaudited)" + flaky
602
+ if ev == "unverified":
603
+ return "wrong answer (evidence unverified)" + flaky
604
+ if ev in NO_ORACLE_TEXT:
605
+ return f"wrong answer (no oracle: {NO_ORACLE_NAME[ev]})" + flaky
606
+ if ev == "legality":
607
+ return "wrong answer (legality program's kernel)" + flaky
608
+ if disposition == "valid":
609
+ return "wrong answer (confirmed)" + flaky
610
+ return "wrong answer" + flaky
611
+ if ev in UNCREDITED and o in ("correct_on_device", "unobservable"):
612
+ return {"unverified": UNVERIFIED_TEXT, "legality": LEGALITY_TEXT,
613
+ **NO_ORACLE_TEXT}[ev] + flaky
614
+ if o == "unobservable":
615
+ return "compiled and ran; nothing checked (unobservable)" + flaky
616
+ if o == "correct_on_device":
617
+ if unaudited:
618
+ return "correct on device (oracle unaudited)" + flaky
619
+ if ev == "copy_mode_only" and rec["xnack"] == 1 and not nv:
620
+ return ("correct on device (copy-mode evidence; not separable at "
621
+ "HSA_XNACK=1)" + flaky)
622
+ if ev == "copy_mode_only" and nv and rec["xnack"] != nv_modes(
623
+ rec["toolchain"])[0]: # Managed shares the heap
624
+ return ("correct on device (copy-mode evidence; not separable in "
625
+ f"{nv_mode_name(rec['toolchain'], rec['xnack'])} memory)"
626
+ + flaky)
627
+ if ev == "probe" and rec["reason"].startswith(
628
+ "compile_error_termination"):
629
+ return ("compile-time error termination as required (probe)"
630
+ + flaky)
631
+ if ev == "probe" and "compile message: " in rec["reason"]:
632
+ return ("correct on device; compile-time message as required "
633
+ "(probe)" + flaky)
634
+ if ev == "probe":
635
+ return "error termination as required (negative probe)" + flaky
636
+ return "correct on device" + flaky
637
+ return o + flaky
638
+
639
+
640
+ COMPANION_SUFFIX = "-binop" # offload.engine.acc_c_gen.BINOP_SUFFIX
641
+
642
+
643
+ def is_companion(program_id: str) -> bool:
644
+ """A companion form of an atom's program (the
645
+ OpenACC C port's atomic update as written, beside the atom's program),
646
+ not the atom's program: kept out of the one-row-per-atom tables."""
647
+ return program_id.endswith(COMPANION_SUFFIX)
648
+
649
+
650
+ def companion_section(results: Path) -> list[str]:
651
+ """Markdown lines listing the companion programs' cells of `results`
652
+ (none: no lines)."""
653
+ results = Path(results)
654
+ if not results.is_file():
655
+ return []
656
+ recs = [json.loads(line) for line in results.read_text().splitlines()
657
+ if line.strip()]
658
+ recs = sorted((r for r in recs if is_companion(r["program_id"])),
659
+ key=lambda r: (r["program_id"], r["toolchain"],
660
+ r["xnack"]))
661
+ if not recs:
662
+ return []
663
+ out = ["## Companion forms (not in the per-atom counts)", "",
664
+ "A companion program is another legal form of an atom's program "
665
+ "(the OpenACC C port's atomic update "
666
+ "x = x + e1 + e2, legal by OpenACC 3.4 §2.12, beside the atom's "
667
+ "program, which writes x += e1 + e2). Its cells are listed here "
668
+ "and go through the confirmer; they are not in the tables above.",
669
+ "", "| program | atom | toolchain | mode | outcome | reason |",
670
+ "|---|---|---|---|---|---|"]
671
+ for r in recs:
672
+ out.append(f"| {r['program_id']} | `{r['primary_atom']}` | "
673
+ f"{TC_NAME.get(r['toolchain'], r['toolchain'])} | "
674
+ f"{r['xnack']} | {r['outcome']} | "
675
+ f"{_md(r.get('reason', '')[:120])} |")
676
+ return out + [""]
677
+
678
+
679
+ def build_table(results: Path, audit: Path | None = AUDIT,
680
+ source_of=generated_source) -> list[Row]:
681
+ """One Row per matrix cell (program, toolchain, HSA_XNACK).
682
+
683
+ Raises ValueError on a duplicate cell. ``audit`` is the committed audit
684
+ summary (dispositions per version and atom); ``source_of(program_id,
685
+ atom, version)`` gives a program's source for the diagnosed-line check.
686
+ """
687
+ recs = [json.loads(line) for line in Path(results).read_text().splitlines()
688
+ if line.strip()]
689
+ # One program per atom; the companion forms (the
690
+ # OpenACC C -binop programs) are listed by companion_section
691
+ recs = [r for r in recs if not is_companion(r["program_id"])]
692
+ # GCC: toolchains kept out of the support tables on purpose
693
+ # (REPORT_EXCLUDED); their rows are dropped here, never an error
694
+ recs = [r for r in recs if r["toolchain"] not in REPORT_EXCLUDED]
695
+ present = {r["toolchain"] for r in recs}
696
+ # Rows of a toolchain without a column fail loudly
697
+ # (they were dropped)
698
+ every_twin = [w for t in ALL_TOOLCHAINS for w in _reg().twins_of(t)]
699
+ unknown = sorted(present - set(ALL_TOOLCHAINS) - set(every_twin) -
700
+ set(NVIDIA_TOOLCHAINS))
701
+ if unknown:
702
+ raise ValueError(f"{results}: rows of toolchains with no support-"
703
+ f"table column: {unknown} (offload.engine.report."
704
+ "ALL_TOOLCHAINS)")
705
+ TOOLCHAINS[:] = [t for t in ALL_TOOLCHAINS if t in present
706
+ or present & set(_reg().twins_of(t))]
707
+ G90[:] = [g90_of(t) for t in ALL_TOOLCHAINS if g90_of(t) in present]
708
+ TWINS[:] = [w for w in every_twin if w in present]
709
+ MI300A[:] = [t for t in ALL_TOOLCHAINS if t in present]
710
+ NV[:] = [t for t in NVIDIA_TOOLCHAINS if t in present]
711
+ seen: set[tuple] = set()
712
+ by_cell: dict[tuple, dict] = {}
713
+ for r in recs:
714
+ key = (r["program_id"], r["toolchain"], r["xnack"], r.get("opt", ""))
715
+ if key in seen:
716
+ raise ValueError(f"duplicate matrix cell {key}")
717
+ seen.add(key)
718
+ by_cell[key] = r
719
+ disp = _audit_map(audit)
720
+ rows = []
721
+ for (pid, tc, x, opt), r in by_cell.items():
722
+ if tc in NVIDIA_TOOLCHAINS: # Its other mode
723
+ other = next((by_cell[k] for k in ((pid, tc, y, opt)
724
+ for y in nv_modes(tc)
725
+ if y != x) if k in by_cell),
726
+ None)
727
+ else:
728
+ other = by_cell.get((pid, tc, 1 - x, opt))
729
+ d = disp.get((r.get("version", 0), r["primary_atom"]), "")
730
+ if tc in NVIDIA_TOOLCHAINS:
731
+ # Dispositions are per toolchain: an NVIDIA cell
732
+ # reads the audit of its own toolchain and mode (none: no
733
+ # disposition, so no failure reads as confirmed)
734
+ d = _nv_audit(r).get((r.get("version", 0), r["primary_atom"]),
735
+ "")
736
+ if pid.endswith("-hostdev") or r.get("scope") == "host+device":
737
+ d = "" # A pattern program is not the atom's oracle
738
+ legal = r.get("legal", True)
739
+ if legal:
740
+ outcome = r["outcome"]
741
+ else:
742
+ outcome = r["outcome"] if r["outcome"] in (
743
+ "rejected", "harness_error") else "not_rejected"
744
+ reason = _root_reason(r)
745
+ label = _label(r, other, d)
746
+ if (x == HEADLINE and legal and other is None
747
+ and r.get("evidence") == "copy_mode_only"
748
+ and r["outcome"] not in _COMPILE_TIME):
749
+ label += f"; {COPY_NOT_RUN}"
750
+ why, excluded = "", False
751
+ if legal and outcome == "rejected":
752
+ atom = r["primary_atom"]
753
+ listed = confound(atom, tc, reason)
754
+ caveat = confound(atom, tc, reason, CAVEATS)
755
+ line, at_directive = ("", False) if listed or caveat else \
756
+ _line_confound(atom, reason,
757
+ source_of(pid, atom, r.get("version", 0)))
758
+ if listed or at_directive:
759
+ why, excluded = listed or line, True
760
+ label = f"rejected (not counted: {why})"
761
+ elif caveat or line:
762
+ why = caveat or line
763
+ label = "rejected ‡"
764
+ scope = r.get("scope") or ("host+device" if pid.endswith(
765
+ "-hostdev") else "device")
766
+ use = ""
767
+ if scope != "device":
768
+ from offload.engine.offload_scope import scope_of
769
+ src = source_of(pid, r["primary_atom"], r.get("version", 0))
770
+ use = scope_of(src)[1] if src else ""
771
+ basis = ""
772
+ if legal and r.get("evidence") in NO_ORACLE_TEXT:
773
+ from offload.engine.gen import no_oracle_of
774
+ src = source_of(pid, r["primary_atom"], r.get("version", 0))
775
+ noo = no_oracle_of(src) if src else None
776
+ basis = noo[2] if noo else ""
777
+ rows.append(Row(
778
+ atom=r["primary_atom"], toolchain=tc, xnack=x, outcome=outcome,
779
+ flaky=bool(r.get("flaky")), version=r.get("version", 0),
780
+ program_id=pid, legal=legal, violates=r.get("violates"),
781
+ evidence=r.get("evidence", "construct"), audit=d,
782
+ label=label, reason=reason, confound=why, excluded=excluded,
783
+ needs=tuple(r.get("needs", ())), thp=r.get("thp", ""),
784
+ basis=basis, lang=r.get("lang", "f90"), scope=scope, use=use))
785
+ labels = {(r.program_id, r.toolchain, r.xnack): r.label for r in rows}
786
+ cells = {(r.program_id, r.toolchain, r.xnack): r for r in rows}
787
+ rows = [replace(r, presence_x1=(PRESENCE_X1.get(r.program_id) or {})
788
+ .get(r.toolchain, "")) if r.xnack == HEADLINE and r.legal
789
+ else r for r in rows]
790
+ def three(r):
791
+ """The portable verdict over the three cells."""
792
+ copy = cells.get((r.program_id, r.toolchain, COPY))
793
+ if _mode_class(r, copy) == "requirement_refused":
794
+ return "requirement_refused"
795
+ v2 = _portable(r, copy)
796
+ if not G90: # no gfx90a run in this matrix: the two-mode verdict
797
+ return v2
798
+ g = cells.get((r.program_id, g90_of(r.toolchain), COPY))
799
+ return portable3(v2, g.label if g else None)
800
+ rows = [replace(r, copy_label=labels.get(
801
+ (r.program_id, r.toolchain, COPY), NOT_RUN),
802
+ portable=three(r),
803
+ mode_class=_mode_class(r, cells.get((r.program_id, r.toolchain,
804
+ COPY))),
805
+ arch_class=_arch_class(r, cells.get((r.program_id, r.toolchain,
806
+ COPY)),
807
+ cells.get((r.program_id, g90_of(r.toolchain),
808
+ COPY))))
809
+ if r.xnack == HEADLINE and r.toolchain in TOOLCHAINS else r
810
+ for r in rows]
811
+ rows = [_nv_headline(r, cells) if r.toolchain in NV else r
812
+ for r in rows]
813
+ # A managed cell relabelled by its headline row's class
814
+ rows = [cells[(r.program_id, r.toolchain, r.xnack)]
815
+ if r.toolchain in NV and r.xnack != nv_modes(r.toolchain)[0]
816
+ else r for r in rows]
817
+ order = {t: i for i, (t, _) in enumerate(columns())}
818
+
819
+ def mode_rank(r):
820
+ if r.toolchain in NV:
821
+ return nv_modes(r.toolchain).index(r.xnack)
822
+ return -r.xnack
823
+ rows.sort(key=lambda r: (r.version, not r.legal, r.atom, r.program_id,
824
+ order.get(r.toolchain, 99), mode_rank(r)))
825
+ return annotate_gate(rows)
826
+
827
+
828
+ def _nv_headline(r: "Row", cells: dict) -> "Row":
829
+ """An NVIDIA headline row (the machine's headline mode,
830
+ separate memory) with its other mode's label (managed), its portable
831
+ verdict over both modes and the class of a difference between them;
832
+ other rows unchanged."""
833
+ modes = nv_modes(r.toolchain)
834
+ if r.xnack != modes[0]:
835
+ return r
836
+ other = cells.get((r.program_id, r.toolchain, modes[1])) \
837
+ if len(modes) > 1 else None
838
+ cls = _nv_mode_class(r, other)
839
+ if cls == "managed_shares_storage" and other is not None:
840
+ other = replace(other, label=NV_SHARED_TEXT)
841
+ cells[(other.program_id, other.toolchain, other.xnack)] = other
842
+ port = _nv_portable(r, other) if len(modes) > 1 else _one_mode(r)
843
+ if cls == "managed_shares_storage" and _ok(r.label):
844
+ port = "managed_unverified"
845
+ return replace(r, copy_label=other.label if other else NOT_RUN,
846
+ portable=port, mode_class=cls)
847
+
848
+
849
+ # The portable verdict of an NVIDIA toolchain: correct in both of
850
+ # its machine's modes (separate and managed memory on wingtip). The keys
851
+ # are PORTABLE's, the copy-mode role taken by separate memory (x0) and the
852
+ # shared one by managed memory (x1).
853
+ NV_PORTABLE = {
854
+ "portable": "correct in separate and in managed memory",
855
+ "x1_only": "correct in managed memory only",
856
+ "x0_only": "correct in separate memory only",
857
+ "neither": "an observed failure in both modes",
858
+ "x0_fail_x1_unverified": "failed in separate memory; managed not "
859
+ "verifiable",
860
+ "x1_fail_x0_unverified": "failed in managed memory; separate not "
861
+ "verifiable",
862
+ "not_verifiable": "neither mode gives an observable verdict",
863
+ "needs_zero_copy": "the program needs zero-copy memory (unified "
864
+ "memory, which wingtip does not have) by design",
865
+ "managed_unverified": "correct in separate memory; the managed-memory "
866
+ "cell is not verifiable (managed_shares_storage)",
867
+ "managed_not_run": "no managed-memory run",
868
+ # a toolchain that runs one memory mode only (GCC: separate memory)
869
+ "one_mode_correct": "correct in the one memory mode the toolchain runs",
870
+ "one_mode_failure": "an observed failure in the one memory mode the toolchain runs",
871
+ }
872
+
873
+
874
+ def _one_mode(r: "Row") -> str:
875
+ """The verdict of a toolchain that runs one memory mode only."""
876
+ if not r.legal:
877
+ return ""
878
+ if "zero_copy" in r.needs and not _ok(r.label):
879
+ return "needs_zero_copy"
880
+ return "one_mode_correct" if _ok(r.label) else \
881
+ "one_mode_failure" if _failure(r.label) else "not_verifiable"
882
+
883
+
884
+ def _nv_portable(r: "Row", man: "Row | None") -> str:
885
+ if not r.legal:
886
+ return ""
887
+ if "zero_copy" in r.needs and not _ok(r.label):
888
+ return "needs_zero_copy"
889
+ if man is None:
890
+ return "managed_not_run"
891
+ return portable_of(man.label, r.label, False,
892
+ r.evidence == "copy_mode_only")
893
+
894
+
895
+ # Classes of a difference between an NVIDIA toolchain's separate
896
+ # and managed memory cells (catalog/mode_differences.yaml nvhpc_entries)
897
+ NV_MODE_CLASSES = {
898
+ "separate_failure": "correct in managed memory, wrong in separate "
899
+ "memory, the program's mapping conforming: a "
900
+ "compiler failure with separate host and device "
901
+ "memory (the copy-mode failure), with a finding",
902
+ "managed_failure": "correct in separate memory, wrong in managed "
903
+ "memory: a failure specific to managed memory, with a "
904
+ "finding",
905
+ "both_wrong": "wrong in both modes, in different ways: not a "
906
+ "portability result",
907
+ "needs_unified_memory": "the program requires unified memory by design "
908
+ "(requires unified_shared_memory, self_maps): "
909
+ "failing without it is not a portability result",
910
+ "requirement_refused": "the runtime or compiler refuses a requires "
911
+ "directive in one mode (supporting a requirement "
912
+ "is implementation defined): conforming",
913
+ "intermittent": "a failure that shows in one mode by chance (a race "
914
+ "or a flaky run), not the mode",
915
+ "not_verifiable": "neither cell gives a verdict the other contradicts "
916
+ "(unobservable, ignored or a harness error on one "
917
+ "side): not a portability result",
918
+ "managed_shares_storage": "a copy_mode_only program whose shared-memory "
919
+ "probe is local data (separate in managed "
920
+ "memory) while its checked arrays are "
921
+ "allocatable (managed memory: the device "
922
+ "shares their storage, which the spec allows): "
923
+ "the copy-semantics check says nothing in "
924
+ "managed memory; rerun with the probe on the "
925
+ "heap it passes (tools/campaigns/nvhpc_managed_probe.py)",
926
+ }
927
+ # the managed-memory cell of a managed_shares_storage difference
928
+ NV_SHARED_TEXT = ("not verifiable in managed memory (the copy-mode check "
929
+ "reads storage managed memory shares)")
930
+
931
+
932
+ def nv_mode_entry(program_id: str, toolchain: str) -> dict | None:
933
+ """The catalog/mode_differences.yaml nvhpc_entries entry of a
934
+ (program, NVIDIA toolchain)."""
935
+ from fnmatch import fnmatchcase
936
+ for e in MODE_DIFFERENCES.get("nvhpc_entries") or []:
937
+ if any(fnmatchcase(program_id, p) for p in e["programs"]) and any(
938
+ fnmatchcase(toolchain, t) for t in e["toolchains"]):
939
+ return e
940
+ return None
941
+
942
+
943
+ def _nv_mode_class(r: "Row", other: "Row | None") -> str:
944
+ if not r.legal or other is None or _mode_key(r) == _mode_key(other):
945
+ return ""
946
+ e = nv_mode_entry(r.program_id, r.toolchain)
947
+ return e["class"] if e else "unclassified"
948
+
949
+
950
+ def nv_mode_class_problem(cls: str, sep_label: str, man_label: str
951
+ ) -> str | None:
952
+ """Why an NVIDIA mode class does not fit the separate and managed
953
+ labels of its cell (None when it fits)."""
954
+ fs, fm = _failure(sep_label), _failure(man_label)
955
+ need = {"separate_failure": (True, False),
956
+ "managed_failure": (False, True),
957
+ "both_wrong": (True, True)}.get(cls)
958
+ if need is not None and (fs, fm) != need:
959
+ return (f"class {cls} needs separate "
960
+ f"{'failing' if need[0] else 'not failing'} and managed "
961
+ f"{'failing' if need[1] else 'not failing'}; the cells are "
962
+ f"{sep_label!r} / {man_label!r}")
963
+ if cls in ("needs_unified_memory", "requirement_refused") and not (
964
+ fs or fm):
965
+ return f"class {cls} needs a failing cell"
966
+ return None
967
+
968
+
969
+ # The verdict a user can rely on for portable code is "correct in
970
+ # both memory modes" (HSA_XNACK=0: separate host and device memory, as on a
971
+ # discrete GPU; HSA_XNACK=1: unified memory, where a missing or wrong map
972
+ # often still gives the right answer). Values of Row.portable (headline
973
+ # legal rows; "" elsewhere):
974
+ PORTABLE = {
975
+ "portable": "correct at HSA_XNACK=0 and at HSA_XNACK=1",
976
+ "x1_only": "correct at HSA_XNACK=1 only (unified memory)",
977
+ "x0_only": "correct at HSA_XNACK=0 only (copy mode)",
978
+ "neither": "an observed failure in both modes (rejected, wrong "
979
+ "answer, crash, host fallback, a probe that did not "
980
+ "terminate, or a presence check failing at HSA_XNACK=1)",
981
+ "x0_fail_x1_unverified": "failed at HSA_XNACK=0; HSA_XNACK=1 not "
982
+ "verifiable (nothing observable there, or a "
983
+ "copy_mode_only program whose HSA_XNACK=1 "
984
+ "check skips what the copy mode tests)",
985
+ "x1_fail_x0_unverified": "failed at HSA_XNACK=1; HSA_XNACK=0 not "
986
+ "verifiable",
987
+ "requirement_refused": "the runtime refuses a requires directive in one "
988
+ "mode (supporting a requirement is "
989
+ "implementation defined, 5.2 §8.2): a conforming "
990
+ "refusal, not a failure",
991
+ "not_verifiable": "neither mode gives an observable verdict "
992
+ "(unobservable, accepted and ignored, no oracle, "
993
+ "unverified, harness error, a rejection not counted)",
994
+ "needs_zero_copy": "the program needs zero-copy memory (HSA_XNACK=1) "
995
+ "by design; not a portability result",
996
+ "x0_not_run": "no HSA_XNACK=0 run",
997
+ # The third cell, gfx90a (Tioga MI250X, a discrete GPU, at
998
+ # HSA_XNACK=0): "portable" needs it correct too
999
+ "gfx942_only": "correct in both MI300A modes, an observed failure on "
1000
+ "the discrete gfx90a (MI250X)",
1001
+ "gfx90a_unverified": "correct in both MI300A modes; the gfx90a cell "
1002
+ "gives no verdict (unobservable, ignored, harness "
1003
+ "error, ...)",
1004
+ "gfx90a_not_run": "correct in both MI300A modes; no gfx90a run",
1005
+ }
1006
+
1007
+
1008
+ def portable3(v2: str, g90_label: str | None) -> str:
1009
+ """The portable verdict over the three cells of a compiler:
1010
+ `v2`, the two-mode MI300A verdict (portable_of), and the gfx90a cell's
1011
+ label (None: no gfx90a row). Only a cell correct in both MI300A modes
1012
+ can be portable; it stays so when the gfx90a cell is correct too, else
1013
+ it is gfx942_only (an observed failure on gfx90a), gfx90a_unverified or
1014
+ gfx90a_not_run. Every other two-mode verdict already says the cell is
1015
+ not portable and stands; the gfx90a column and the arch-difference
1016
+ class (ARCH_CLASSES) say what gfx90a adds."""
1017
+ if v2 != "portable":
1018
+ return v2
1019
+ if g90_label is None:
1020
+ return "gfx90a_not_run"
1021
+ if _failure(g90_label):
1022
+ return "gfx942_only"
1023
+ return "portable" if _ok(g90_label) else "gfx90a_unverified"
1024
+
1025
+
1026
+ MODE_DIFF_FILE = Path(__file__).resolve().parent.parent.parent / "catalog" / \
1027
+ "mode_differences.yaml"
1028
+ # Classes of a difference between the memory modes
1029
+ MODE_CLASSES = {
1030
+ "copy_mode_failure": "correct at HSA_XNACK=1, wrong at 0, and the "
1031
+ "program's mapping is conforming (it would be "
1032
+ "correct with separate host and device memory): a "
1033
+ "compiler copy-mode failure, with a finding",
1034
+ "unified_memory_failure": "correct at HSA_XNACK=0, wrong at 1: a "
1035
+ "failure specific to unified memory",
1036
+ "needs_unified_memory": "the program requires unified memory by design "
1037
+ "(requires unified_shared_memory or self_maps, "
1038
+ "the self map-type modifier): failing or "
1039
+ "refusing to run at HSA_XNACK=0 is not a "
1040
+ "portability result",
1041
+ "requirement_refused": "the runtime refuses a requires directive at "
1042
+ "HSA_XNACK=0 and terminates before the region "
1043
+ "(supporting a requirement is implementation "
1044
+ "defined, 5.2 §8.2): conforming, not a wrong "
1045
+ "answer",
1046
+ "both_wrong": "wrong in both modes, in different ways: not a "
1047
+ "portability result",
1048
+ "no_finding_yet": "a difference between the modes that no finding explains yet: shown "
1049
+ "in the table and not counted as a compiler failure",
1050
+ "x1_oracle_blind": "the program's check runs only in copy mode (its "
1051
+ "HSA_XNACK=1 cell reads correct with copy-mode "
1052
+ "evidence, which checks nothing the mode hides) and "
1053
+ "a copy-independent check (tools/campaigns/presence_x1.py, the "
1054
+ "reproducer) shows the compiler wrong at "
1055
+ "HSA_XNACK=1 too: a failure in both modes, not a "
1056
+ "copy-mode failure",
1057
+ }
1058
+
1059
+
1060
+ # Classes of a difference between a compiler's MI300A copy-mode
1061
+ # cell (gfx942, HSA_XNACK=0) and its gfx90a cell (MI250X, HSA_XNACK=0):
1062
+ # both have separate host and device memory, so a difference is the GPU,
1063
+ # its runtime path or what the APU's copy mode does not emulate
1064
+ ARCH_CLASSES = {
1065
+ "gfx90a_failure": "correct on MI300A, wrong on gfx90a, the program's "
1066
+ "mapping is conforming: a compiler or runtime failure "
1067
+ "specific to gfx90a (or to the discrete GPU), with a "
1068
+ "finding",
1069
+ "x0_emulation_gap": "correct at HSA_XNACK=0 on MI300A, wrong on the "
1070
+ "discrete gfx90a because something reaches device "
1071
+ "code without a map that the APU's copy mode still "
1072
+ "makes accessible: the compiler fails to map it "
1073
+ "(a mapping failure HSA_XNACK=0 on MI300A hides), "
1074
+ "with a finding",
1075
+ "gfx942_failure": "wrong at HSA_XNACK=0 on MI300A, correct on gfx90a: "
1076
+ "a failure specific to the MI300A (APU) copy mode, "
1077
+ "with a finding",
1078
+ "both_wrong": "wrong on both GPUs, in different ways: not a "
1079
+ "portability result",
1080
+ "needs_unified_memory": "the program requires unified memory by "
1081
+ "design; failing or refusing without it is not "
1082
+ "a portability result",
1083
+ "requirement_refused": "the runtime refuses a requires directive on "
1084
+ "one GPU (supporting a requirement is "
1085
+ "implementation defined, 5.2 §8.2): conforming",
1086
+ "no_finding_yet": "a difference between the GPUs that no finding explains yet: shown "
1087
+ "in the table and not counted as a compiler failure",
1088
+ "intermittent": "a failure the finding shows to be intermittent (a "
1089
+ "race): it showed in one cell and not in the other by "
1090
+ "chance; the same program fails on both GPUs in other "
1091
+ "runs",
1092
+ "not_verifiable": "neither cell gives a verdict the other contradicts "
1093
+ "(e.g. unobservable on one GPU, ignored or a harness "
1094
+ "error on the other): not a portability result",
1095
+ }
1096
+
1097
+
1098
+ def load_mode_differences(path: Path = MODE_DIFF_FILE) -> dict:
1099
+ """catalog/mode_differences.yaml (arch_entries),
1100
+ checked."""
1101
+ import yaml
1102
+ doc = yaml.safe_load(Path(path).read_text()) or {}
1103
+ for e in doc.get("entries") or []:
1104
+ if e.get("class") not in MODE_CLASSES:
1105
+ raise ValueError(f"{path}: unknown class in {e}")
1106
+ for e in doc.get("arch_entries") or []:
1107
+ if e.get("class") not in ARCH_CLASSES:
1108
+ raise ValueError(f"{path}: unknown arch class in {e}")
1109
+ for e in doc.get("nvhpc_entries") or []:
1110
+ if e.get("class") not in NV_MODE_CLASSES:
1111
+ raise ValueError(f"{path}: unknown nvhpc class in {e}")
1112
+ return doc
1113
+
1114
+
1115
+ MODE_DIFFERENCES = load_mode_differences() if MODE_DIFF_FILE.is_file() \
1116
+ else {"entries": []}
1117
+
1118
+
1119
+ def mode_entry(program_id: str, toolchain: str) -> dict | None:
1120
+ """The catalog/mode_differences.yaml entry of a (program, toolchain)."""
1121
+ from fnmatch import fnmatchcase
1122
+ for e in MODE_DIFFERENCES.get("entries") or []:
1123
+ if any(fnmatchcase(program_id, p) for p in e["programs"]) and any(
1124
+ fnmatchcase(toolchain, t) for t in e["toolchains"]):
1125
+ return e
1126
+ return None
1127
+
1128
+
1129
+ def arch_entry(program_id: str, toolchain: str) -> dict | None:
1130
+ """The catalog/mode_differences.yaml arch_entries entry of a (program,
1131
+ gfx942 toolchain)."""
1132
+ from fnmatch import fnmatchcase
1133
+ for e in MODE_DIFFERENCES.get("arch_entries") or []:
1134
+ if any(fnmatchcase(program_id, p) for p in e["programs"]) and any(
1135
+ fnmatchcase(base_name(toolchain), t)
1136
+ for t in e["toolchains"]):
1137
+ return e
1138
+ return None
1139
+
1140
+
1141
+ def _arch_class(r: "Row", copy: "Row | None", g90: "Row | None") -> str:
1142
+ """The class of a difference between the MI300A copy-mode
1143
+ cell and the gfx90a cell of a legal headline row ("" when they agree
1144
+ or one is missing)."""
1145
+ if r.xnack != HEADLINE or not r.legal or copy is None or g90 is None \
1146
+ or _mode_key(copy) == _mode_key(g90):
1147
+ return ""
1148
+ e = arch_entry(r.program_id, r.toolchain)
1149
+ return e["class"] if e else "unclassified"
1150
+
1151
+
1152
+ def _mode_class(r: "Row", copy: "Row | None") -> str:
1153
+ if r.xnack != HEADLINE or not r.legal or copy is None or \
1154
+ _mode_key(r) == _mode_key(copy):
1155
+ return ""
1156
+ e = mode_entry(r.program_id, r.toolchain)
1157
+ return e["class"] if e else "unclassified"
1158
+
1159
+
1160
+ PRESENCE_X1_FILE = Path(__file__).resolve().parent.parent.parent / "tests" / \
1161
+ "data" / "presence_x1_summary.json"
1162
+ # {program id: {toolchain: outcome}}: the copy-mode programs' presence
1163
+ # checks run at HSA_XNACK=1 (tools/campaigns/presence_x1.py)
1164
+ PRESENCE_X1 = (json.loads(PRESENCE_X1_FILE.read_text())["programs"]
1165
+ if PRESENCE_X1_FILE.is_file() else {})
1166
+
1167
+
1168
+ def _ok(label: str) -> bool:
1169
+ """A cell the dashboard counts as correct (status ok); a
1170
+ compile-time probe that terminated as required is a pass too."""
1171
+ return label.startswith(("correct on device", "error termination",
1172
+ "compile-time error termination as required"))
1173
+
1174
+
1175
+ COPY_EVIDENCE_X1 = "correct on device (copy-mode evidence"
1176
+ FAILURE_PREFIXES = ("rejected", "wrong answer", "crash", "fell back",
1177
+ "no error termination", "compile-time probe failed",
1178
+ "fails in copy mode")
1179
+
1180
+
1181
+ def _failure(label: str) -> bool:
1182
+ """An observed failure (a counted rejection, a wrong answer, a crash,
1183
+ a host fallback, a probe that did not terminate)."""
1184
+ return label.startswith(FAILURE_PREFIXES) and \
1185
+ not label.startswith("rejected (not counted")
1186
+
1187
+
1188
+ def mode_class_problem(cls: str, x1_label: str, x0_label: str
1189
+ ) -> str | None:
1190
+ """Why a memory-mode class does not fit the
1191
+ headline (HSA_XNACK=1) and copy-mode labels of its cell (None when it
1192
+ fits): the class says which modes fail."""
1193
+ f1, f0 = _failure(x1_label), _failure(x0_label)
1194
+ need = {"copy_mode_failure": (False, True),
1195
+ "x1_oracle_blind": (False, True),
1196
+ "unified_memory_failure": (True, False),
1197
+ "both_wrong": (True, True)}.get(cls)
1198
+ if need is not None and (f1, f0) != need:
1199
+ return (f"class {cls} needs HSA_XNACK=1 "
1200
+ f"{'failing' if need[0] else 'not failing'} and HSA_XNACK=0 "
1201
+ f"{'failing' if need[1] else 'not failing'}; the cells are "
1202
+ f"{x1_label!r} / {x0_label!r}")
1203
+ if cls in ("needs_unified_memory", "requirement_refused") and not f0:
1204
+ return (f"class {cls} needs a failing HSA_XNACK=0 cell; it is "
1205
+ f"{x0_label!r}")
1206
+ return None
1207
+
1208
+
1209
+ def portable_of(x1_label: str, x0_label: str,
1210
+ x1_hidden_failure: bool = False,
1211
+ x1_copy_only: bool | None = None) -> str:
1212
+ """The portable verdict of a headline label and its copy-mode label.
1213
+ A copy_mode_only cell's HSA_XNACK=1 check is skipped under unified
1214
+ memory: it counts as correct only next to a correct copy-mode cell,
1215
+ never as evidence on its own (`x1_copy_only` comes from
1216
+ the program's evidence, not from the label, which an unaudited oracle
1217
+ changes; without it the label decides). `x1_hidden_failure`: the
1218
+ program's presence checks fail when run at HSA_XNACK=1."""
1219
+ if x1_copy_only is None:
1220
+ x1_copy_only = x1_label.startswith(COPY_EVIDENCE_X1)
1221
+
1222
+ def state(label, other_ok, copy_only, hidden):
1223
+ if hidden or _failure(label):
1224
+ return "fail"
1225
+ if _ok(label) and (other_ok or not copy_only):
1226
+ return "ok"
1227
+ return "unv"
1228
+ b = state(x0_label, True, False, False)
1229
+ a = state(x1_label, b == "ok", x1_copy_only, x1_hidden_failure)
1230
+ return {("ok", "ok"): "portable", ("ok", "fail"): "x1_only",
1231
+ ("ok", "unv"): "x1_only", ("fail", "ok"): "x0_only",
1232
+ ("unv", "ok"): "x0_only", ("fail", "fail"): "neither",
1233
+ ("unv", "fail"): "x0_fail_x1_unverified",
1234
+ ("fail", "unv"): "x1_fail_x0_unverified"}.get(
1235
+ (a, b), "not_verifiable")
1236
+
1237
+
1238
+ def _portable(r: "Row", copy: "Row | None") -> str:
1239
+ if r.xnack != HEADLINE or not r.legal:
1240
+ return ""
1241
+ if copy is None:
1242
+ return "x0_not_run"
1243
+ if "zero_copy" in r.needs and not _ok(copy.label):
1244
+ return "needs_zero_copy"
1245
+ hidden = r.presence_x1 == "wrong_answer"
1246
+ return portable_of(r.label, copy.label, hidden,
1247
+ r.evidence == "copy_mode_only")
1248
+
1249
+
1250
+ NV_COPY_EVIDENCE_FAMILY = ("correct on device (copy-mode evidence; not "
1251
+ "separable in managed memory)")
1252
+
1253
+
1254
+ def family(label: str) -> str:
1255
+ """Coarse category of a cell label, for the summary counts."""
1256
+ for prefix, fam in (
1257
+ ("correct on device (oracle unaudited)",
1258
+ "correct on device (oracle unaudited)"),
1259
+ # An NVIDIA managed-memory cell
1260
+ # is no HSA_XNACK=1 cell
1261
+ ("correct on device (copy-mode evidence; not separable in ",
1262
+ NV_COPY_EVIDENCE_FAMILY),
1263
+ ("correct on device (copy-mode", "correct on device (copy-mode evidence, HSA_XNACK=1)"),
1264
+ ("correct on device", "correct on device"),
1265
+ (NO_ORACLE_TEXT["hint_only"],
1266
+ "accepted; conforms vacuously (no oracle)"),
1267
+ (NO_ORACLE_TEXT["implementation_defined"],
1268
+ "implementation-defined (no oracle)"),
1269
+ (NO_ORACLE_TEXT["equivalent_default"],
1270
+ "equivalent to the default (no oracle)"),
1271
+ (LEGALITY_TEXT, "accepted; oracle is the illegal variant"),
1272
+ (UNVERIFIED_TEXT, "accepted; unverified"),
1273
+ ("compiled and ran", "unobservable"),
1274
+ ("rejected (not counted",
1275
+ "rejected, not counted (another feature)"),
1276
+ ("rejected", "rejected"),
1277
+ ("accepted, ignored", "accepted, ignored"),
1278
+ ("fell back", "fell back to host"),
1279
+ ("wrong answer (confirmed)", "wrong answer (confirmed)"),
1280
+ ("wrong answer (oracle unaudited)", "wrong answer (oracle unaudited)"),
1281
+ ("wrong answer", "wrong answer (other)"),
1282
+ ("crash", "crash"),
1283
+ ("fails in copy mode", "needs zero-copy; failed in copy mode"),
1284
+ ("harness error", "harness error"),
1285
+ (NV_SHARED_TEXT, "not verifiable (managed memory shares the "
1286
+ "checked storage)"),
1287
+ ("not rejected", "not rejected")):
1288
+ if label.startswith(prefix):
1289
+ return fam
1290
+ return label
1291
+
1292
+
1293
+ # --- claims --------------------------------------------------------------------------
1294
+
1295
+ @dataclass(frozen=True)
1296
+ class Claim:
1297
+ source: str
1298
+ url: str
1299
+ toolchain: str
1300
+ claim: str
1301
+ version_note: str
1302
+ quote: str = ""
1303
+ atoms: tuple = ()
1304
+ pattern: str | None = None
1305
+ form: str | None = None # compile_error | accepted | None
1306
+ general: bool = False # release-level statement: specific claims win
1307
+ versions: tuple | None = None
1308
+ status: str = "provisional"
1309
+ language: str = "Fortran"
1310
+ note: str = ""
1311
+ # None for the old per-model claim files (a Fortran row is
1312
+ # compared with a claim of any language); a generic claim covers the
1313
+ # languages it names unless its document is cross-language
1314
+ cross_language: bool | None = None
1315
+ spec_version: str = ""
1316
+ page: str = ""
1317
+
1318
+
1319
+ @dataclass(frozen=True)
1320
+ class Mismatch:
1321
+ atom: str
1322
+ toolchain: str
1323
+ claimed: str
1324
+ measured: str
1325
+ version: int = 0
1326
+ xnack: tuple = ()
1327
+ source: str = ""
1328
+ url: str = ""
1329
+ quote: str = ""
1330
+ oracle_unaudited: bool = False
1331
+ general: bool = False
1332
+ reason: str = "" # harness reason of the first contradicting cell
1333
+ confound: str = "" # listed caveat of a contradicting cell
1334
+ language: str = "" # language the vendor's statement is about
1335
+ # The contradicting cells (program_id, toolchain, xnack,
1336
+ # outcome), every memory mode and GPU, and the rows' language
1337
+ cells: tuple = ()
1338
+ lang: str = "f90"
1339
+ spec_version: str = ""
1340
+ page: str = ""
1341
+
1342
+
1343
+ def _from_generic(d: dict) -> Claim:
1344
+ """A report Claim of one generic record (offload.engine.confirm.claims)."""
1345
+ atom = d["atom"]
1346
+ pat = any(ch in atom for ch in "*?[")
1347
+ page = d.get("page", "")
1348
+ m = re.search(r"\[([^\]]*)\]\s*$", page)
1349
+ return Claim(source=d["source"], url=d["url"], toolchain=d["toolchain"],
1350
+ claim=d["claim"], version_note=m.group(1) if m else page,
1351
+ quote=d["quote"], atoms=() if pat else (atom,),
1352
+ pattern=atom if pat else None, form=d.get("form") or None,
1353
+ general=bool(d.get("general")),
1354
+ versions=tuple(int(v) for v in str(d.get("versions") or "")
1355
+ .split(",") if v) or None,
1356
+ language=d["language"], note=d.get("note", ""),
1357
+ cross_language=bool(d.get("cross_language")),
1358
+ spec_version=str(d.get("spec_version") or ""), page=page)
1359
+
1360
+
1361
+ def load_claims(path: Path) -> list[Claim]:
1362
+ """The claims of a file, parsed once per version of it."""
1363
+ return list(_claims(str(path), Path(path).stat().st_mtime))
1364
+
1365
+
1366
+ @lru_cache(maxsize=None)
1367
+ def _claims(path: str, mtime: float) -> tuple:
1368
+ return tuple(_parse_claims(Path(path)))
1369
+
1370
+
1371
+ def _parse_claims(path: Path) -> list[Claim]:
1372
+ data = yaml.safe_load(Path(path).read_text()) or {}
1373
+ entries = data.get("claims") or []
1374
+ if entries and "vendor" in entries[0]:
1375
+ return [_from_generic(e) for e in entries]
1376
+ out = []
1377
+ for i, e in enumerate(entries):
1378
+ atoms = list(e.get("atoms") or [])
1379
+ if e.get("atom"):
1380
+ atoms.append(e["atom"])
1381
+ c = Claim(source=e["source"], url=e["url"], toolchain=e["toolchain"],
1382
+ claim=e["claim"], version_note=e.get("version_note", ""),
1383
+ quote=e.get("quote", ""), atoms=tuple(atoms),
1384
+ pattern=e.get("pattern"), form=e.get("form"),
1385
+ general=bool(e.get("general", False)),
1386
+ versions=tuple(e["versions"]) if e.get("versions") else None,
1387
+ status=e.get("status", "provisional"),
1388
+ language=e.get("language", "Fortran"),
1389
+ note=e.get("note", ""))
1390
+ if c.claim not in CLAIM_VALUES:
1391
+ raise ValueError(f"claim {i}: claim must be one of {CLAIM_VALUES}")
1392
+ if c.form not in CLAIM_FORMS:
1393
+ raise ValueError(f"claim {i}: form must be one of {CLAIM_FORMS}")
1394
+ if not (c.atoms or c.pattern):
1395
+ raise ValueError(f"claim {i}: needs atom, atoms or pattern")
1396
+ out.append(c)
1397
+ return out
1398
+
1399
+
1400
+ def _matches(c: Claim, row: Row) -> bool:
1401
+ # A claim about a compiler covers its gfx90a twin too
1402
+ if c.toolchain != base_name(row.toolchain):
1403
+ return False
1404
+ if c.versions is not None and row.version not in c.versions:
1405
+ return False
1406
+ if c.versions is None and row.version >= 60 and c.spec_version and \
1407
+ c.spec_version != "6.0":
1408
+ return False # A 5.x feature claim, not the 6.0 form
1409
+ if row.lang != "c" and c.cross_language is False and \
1410
+ "Fortran" not in c.language:
1411
+ return False
1412
+ atom = row.atom
1413
+ if row.lang == "c":
1414
+ # A C row is compared with the claims about C (the
1415
+ # language field names C), under its Fortran atom name where the
1416
+ # claim lists the do spelling (parallel_for -> parallel_do)
1417
+ if "C" not in c.language.replace("C/C++", "C").split() and \
1418
+ "C/C++" not in c.language:
1419
+ return False
1420
+ from offload.engine.c_gen import fortran_atom
1421
+ if atom not in c.atoms and not (c.pattern and fnmatchcase(
1422
+ atom, c.pattern)):
1423
+ atom = fortran_atom(atom)
1424
+ return atom in c.atoms or (
1425
+ c.pattern is not None and fnmatchcase(atom, c.pattern))
1426
+
1427
+
1428
+ def _state(row: Row) -> str | None:
1429
+ """What a legal cell says about support, for claim comparison."""
1430
+ if not row.legal or row.outcome == "harness_error":
1431
+ return None
1432
+ if row.label.startswith("fails in copy mode"):
1433
+ return None # expected for zero-copy programs; not a feature result
1434
+ if row.label == NV_SHARED_TEXT:
1435
+ return None # Managed memory shares the checked storage
1436
+ if row.excluded:
1437
+ return None # the compiler rejected another feature
1438
+ return {"rejected": "rejected", "accepted_ignored": "ignored",
1439
+ "fell_back_to_host": "fail", "wrong_answer": "fail",
1440
+ "unobservable": "accepted",
1441
+ "correct_on_device": "accepted" if row.evidence in UNCREDITED
1442
+ else "pass"}.get(row.outcome)
1443
+
1444
+
1445
+ def _disagrees(c: Claim, row: Row) -> bool:
1446
+ s = _state(row)
1447
+ if s is None:
1448
+ return False
1449
+ if c.claim == "supported":
1450
+ # A failing cell contradicts a support claim only when
1451
+ # it counts as a compiler failure (a confirmed gate verdict)
1452
+ # (an ungated row keeps its outcome until the gate has seen it,
1453
+ # The rule: claims about rows recorded before the gate)
1454
+ if _failure(row.label) and not (confirmed_failure(row) or
1455
+ row.gate == "ungated"):
1456
+ return False
1457
+ return s in ("rejected", "ignored", "fail")
1458
+ if c.claim == "unsupported":
1459
+ return s != "rejected" if c.form == "compile_error" else s == "pass"
1460
+ return c.form == "accepted" and s == "rejected" # partial
1461
+
1462
+
1463
+ def claim_groups(table: list[Row], claims: Path | list[Claim]
1464
+ ) -> dict[tuple, list[Row]]:
1465
+ """(claim index, atom, version, toolchain) -> the legal rows it covers.
1466
+
1467
+ A general (release-level) claim applies to an atom only when no specific
1468
+ claim for the same toolchain covers that atom.
1469
+ """
1470
+ cl = load_claims(claims) if isinstance(claims, (str, Path)) else claims
1471
+ groups: dict[tuple, list[Row]] = defaultdict(list)
1472
+ by_tc: dict[str, list[tuple[int, Claim]]] = defaultdict(list) # a claim covers one toolchain (and its twin)
1473
+ for i, c in enumerate(cl):
1474
+ by_tc[c.toolchain].append((i, c))
1475
+ table_only = _reg().table_only()
1476
+ for row in table:
1477
+ if not row.legal or row.scope != "device":
1478
+ continue # Host+device patterns are not headline
1479
+ if row.toolchain in table_only:
1480
+ continue # a support-table cell only (MI300X, MI350X): no evidence about a claim
1481
+ base = base_name(row.toolchain)
1482
+ mine = [(i, c) for i, c in by_tc.get(base, ()) if _matches(c, row)]
1483
+ hits = [i for i, c in mine if not c.general] or [i for i, c in mine if c.general]
1484
+ for i in hits:
1485
+ groups[(i, row.atom, row.version, base)].append(row)
1486
+ return groups
1487
+
1488
+
1489
+ def _measured(bad: list[Row], rows: list[Row]) -> str:
1490
+ twins = [r for r in bad if r.toolchain != base_name(r.toolchain)]
1491
+ if twins: # Name the GPU of a twin's cell
1492
+ main = [r for r in bad if r not in twins]
1493
+ mrows = [r for r in rows if r.toolchain == base_name(r.toolchain)]
1494
+ parts = [_measured(main, mrows)] if main else []
1495
+ parts += [f"{_reg().twin_tag(r.toolchain)}: {r.label}" for r in twins]
1496
+ return "; ".join(parts)
1497
+ labels = {r.label for r in bad}
1498
+ if len(labels) == 1 and (len(bad) == len(rows)
1499
+ or "HSA_XNACK=" in bad[0].label):
1500
+ return bad[0].label
1501
+ return "; ".join(r.label if "HSA_XNACK=" in r.label
1502
+ else f"{_mode_word(r.toolchain, r.xnack)}: {r.label}"
1503
+ for r in sorted(bad, key=lambda r: -r.xnack))
1504
+
1505
+
1506
+ def _oracle_dependent(row: Row) -> bool:
1507
+ """A run-time outcome of an atom whose oracle is unaudited. Compile-time
1508
+ results (rejected, ignored with a warning) do not depend on the oracle."""
1509
+ return (row.audit.startswith("unaudited")
1510
+ and row.outcome not in ("rejected", "accepted_ignored"))
1511
+
1512
+
1513
+ def compare_claims(table: list[Row], claims: Path | list[Claim]
1514
+ ) -> list[Mismatch]:
1515
+ """Claims the measurements contradict, one Mismatch per claim, atom,
1516
+ version and toolchain (both memory modes folded into ``measured``)."""
1517
+ cl = load_claims(claims) if isinstance(claims, (str, Path)) else claims
1518
+ out = []
1519
+ for (i, atom, version, tc), rows in claim_groups(table, cl).items():
1520
+ c = cl[i]
1521
+ bad = [r for r in rows if _disagrees(c, r)]
1522
+ if not bad:
1523
+ continue
1524
+ claimed = c.claim + (f" ({c.form.replace('_', ' ')})" if c.form
1525
+ else "")
1526
+ out.append(Mismatch(
1527
+ atom=atom, toolchain=tc, claimed=f"{claimed} [{c.source}]",
1528
+ measured=_measured(bad, rows), version=version,
1529
+ xnack=tuple(sorted({r.xnack for r in bad}, reverse=True)),
1530
+ source=c.source, url=c.url, quote=c.quote,
1531
+ oracle_unaudited=any(_oracle_dependent(r) for r in bad),
1532
+ general=c.general, reason=bad[0].reason, language=c.language,
1533
+ confound=next((r.confound for r in bad if r.confound), ""),
1534
+ cells=tuple((r.program_id, r.toolchain, r.xnack, r.outcome)
1535
+ for r in bad),
1536
+ lang=bad[0].lang, spec_version=c.spec_version, page=c.page))
1537
+ out.sort(key=lambda m: (m.toolchain, m.oracle_unaudited, m.source,
1538
+ m.atom, m.version))
1539
+ return out
1540
+
1541
+
1542
+ # --- citations -----------------------------------------------------------------------
1543
+
1544
+ @lru_cache(maxsize=None)
1545
+ def _yaml(path: Path):
1546
+ return yaml.safe_load(Path(path).read_text())
1547
+
1548
+
1549
+ def _vstr(version: int) -> str:
1550
+ return f"{version // 10}.{version % 10}"
1551
+
1552
+
1553
+ def _spec(version: int) -> str:
1554
+ """'OpenMP 5.2' / 'OpenACC 3.4'."""
1555
+ from offload.engine.catalog import spec_name
1556
+ return spec_name(version)
1557
+
1558
+
1559
+ @lru_cache(maxsize=None)
1560
+ def _acc_section(number: str) -> str | None:
1561
+ """'OpenACC 3.4 §<number>, p.<first page>'."""
1562
+ try:
1563
+ from offload.engine import spec_text
1564
+ secs = spec_text.sections("acc3.4")
1565
+ except Exception:
1566
+ return None
1567
+ for sec in secs.values():
1568
+ if sec.number == number:
1569
+ return f"OpenACC 3.4 §{number}, p.{sec.first_page}"
1570
+ return None
1571
+
1572
+
1573
+ @lru_cache(maxsize=None)
1574
+ def _spec_section(version: int, name: str) -> str | None:
1575
+ """'OpenMP 5.2 §13.8, p.284' for a construct/directive name, or None."""
1576
+ try:
1577
+ from offload.engine import spec_text
1578
+ secs = spec_text.sections(_vstr(version))
1579
+ except Exception: # spec PDF or pdftotext missing: fall back to catalog
1580
+ return None
1581
+ forms = dict.fromkeys([name, name.replace(" ", "_")])
1582
+ for f in forms:
1583
+ for suffix in ("", " Construct", " Directive", " Clause"):
1584
+ s = secs.get(f + suffix)
1585
+ if s is not None:
1586
+ return f"OpenMP {_vstr(version)} §{s.number}, p.{s.first_page}"
1587
+ return None
1588
+
1589
+
1590
+ _COMPOUND = {50: "Combined Constructs",
1591
+ 52: "Combined and Composite Directive Names",
1592
+ 60: "Compound Directive Names"}
1593
+
1594
+
1595
+ # 5.0 clauses without a section of their own, by the construct
1596
+ # section whose syntax lists them (the 5.0 section titles)
1597
+ _HOME_50 = {
1598
+ "aligned": "simd", "safelen": "simd", "simdlen": "simd",
1599
+ "nontemporal": "simd", "collapse": "Worksharing-Loop",
1600
+ "schedule": "Worksharing-Loop", "ordered": "Worksharing-Loop",
1601
+ "num_threads": "parallel", "num_teams": "teams",
1602
+ "thread_limit": "teams", "dist_schedule": "distribute", "bind": "loop",
1603
+ "nowait": "target", "device": "target", "is_device_ptr": "target",
1604
+ "uses_allocators": "target", "device_type": "declare target",
1605
+ "link": "declare target", "use_device_ptr": "target data",
1606
+ "use_device_addr": "target data", "unified_address": "requires",
1607
+ "unified_shared_memory": "requires", "reverse_offload": "requires",
1608
+ "dynamic_allocators": "requires",
1609
+ }
1610
+
1611
+
1612
+ def atom_citation(atom: str, version: int) -> tuple[str, str]:
1613
+ """(citation text, link) for a legal program's primary atom."""
1614
+ parts = atom.split(".")
1615
+ clauses_path, _, _, LINK = _paths(model_of_atom(atom))
1616
+ from offload.engine.catalog import semantic_atoms
1617
+ sem = semantic_atoms().get(atom)
1618
+ if sem is not None: # The spec text that defines the behaviour
1619
+ cites = sem.citations
1620
+ best = [v for v in sorted(cites) if v <= version] or sorted(cites)[:1]
1621
+ return cites[best[-1]], LINK["semantic"]
1622
+ if parts[0] == "acc" and len(parts) >= 3 and parts[1] == "directive":
1623
+ cite = _acc_section(ACC_DIRECTIVE_SECTION.get(parts[2], ""))
1624
+ return cite or "TableGen skeleton entry", LINK["skeleton"]
1625
+ if len(parts) >= 3 and parts[1] == "clause":
1626
+ e = (_yaml(clauses_path).get("clauses") or {}).get(parts[2])
1627
+ if e:
1628
+ forms = sorted(e["forms"], key=lambda f: f["since"])
1629
+ best = [f for f in forms if f["since"] <= version]
1630
+ if not best and parts[0] == "omp":
1631
+ # A 5.0 clause whose syntax is the 5.2 one has no
1632
+ # 5.0 form: cite its 5.0 section, or the section of the
1633
+ # construct whose syntax lists it
1634
+ own = _spec_section(version, parts[2]) or (
1635
+ _spec_section(version, _HOME_50[parts[2]])
1636
+ if version == 50 and parts[2] in _HOME_50 else None)
1637
+ if own:
1638
+ return own, LINK["clauses"]
1639
+ best = best or forms[:1]
1640
+ return best[-1]["citation"], LINK["clauses"]
1641
+ return "catalog clause entry", LINK["clauses"]
1642
+ if len(parts) >= 3 and parts[1] == "directive":
1643
+ name = parts[2].replace("_", " ")
1644
+ cite = _spec_section(version, name)
1645
+ if cite is None:
1646
+ leaves = [c for w in name.split()
1647
+ if (c := _spec_section(version, w))]
1648
+ comp = _spec_section(version, _COMPOUND.get(version, ""))
1649
+ if comp and leaves:
1650
+ cite = f"{comp} (compound); leaves " + "; ".join(
1651
+ x.split(" ", 2)[2] for x in leaves)
1652
+ return cite or "TableGen skeleton entry", LINK["skeleton"]
1653
+ return "catalog entry", LINK["skeleton"]
1654
+
1655
+
1656
+ def rule_citation(rule_id: str | None) -> tuple[str, str]:
1657
+ """(citation text, link) for an illegal variant's rule (OMP52-...)."""
1658
+ if not rule_id:
1659
+ return "no rule", LINK["rules"]
1660
+ ver, _, rid = rule_id.partition("-")
1661
+ model = "acc" if ver.startswith("ACC") else "omp"
1662
+ _, rules_path, _, LINK = _paths(model)
1663
+ try:
1664
+ version = int(ver.removeprefix("OMP").removeprefix("ACC"))
1665
+ except ValueError:
1666
+ return rule_id, LINK["rules"]
1667
+ for r in _yaml(rules_path).get("rules", []):
1668
+ if r["id"] == rid:
1669
+ spec = r.get("spec", {}).get(version) or {}
1670
+ return spec.get("citation", rid), LINK["rules"]
1671
+ return rule_id, LINK["rules"]
1672
+
1673
+
1674
+ # --- rendering -----------------------------------------------------------------------
1675
+
1676
+ def _md(text: str) -> str:
1677
+ return str(text).replace("|", "\\|").replace("\n", " ")
1678
+
1679
+
1680
+ def _clip(text: str, n: int = 170) -> str:
1681
+ text = " ".join(str(text).split())
1682
+ return text if len(text) <= n else text[:n - 1] + "…"
1683
+
1684
+
1685
+ # --- The reproduction gate's status of a failing cell ---------------------
1686
+
1687
+ GATE_STATES = ("confirmed", "decided", "unchecked", "nondeterministic",
1688
+ "decided_not_counted", "ours", "environment", "not_reproduced",
1689
+ "illegal", "not_a_failure", "ungated")
1690
+ GATE_TEXT = {
1691
+ "confirmed": "confirmed (own, or inherited from a compile-time "
1692
+ "signature): counted",
1693
+ "decided": "decided, counts (a decision with counts_as: "
1694
+ "failure): counted",
1695
+ "unchecked": "unchecked (not rerun): not counted here",
1696
+ "nondeterministic": "nondeterministic: not counted",
1697
+ "decided_not_counted": "decided, does not count (a decision)",
1698
+ "ours": "ours (a recorded ruling: our build's failure, not the "
1699
+ "compiler's): not counted",
1700
+ "environment": "environment: not counted",
1701
+ "not_reproduced": "not reproduced: not counted",
1702
+ "illegal": "illegal: not counted",
1703
+ "not_a_failure": "not a failure: not counted",
1704
+ "ungated": "ungated (no gate verdict yet): not counted here",
1705
+ }
1706
+ COUNTED_STATES = ("confirmed", "decided")
1707
+
1708
+
1709
+ def gate_status(rec: dict | None, decision: dict | None) -> str:
1710
+ """The gate column of a failing cell from its verdict record
1711
+ (offload.engine.gate.load_verdicts) and its decision
1712
+ (offload.engine.gate.load_decisions): ``decided`` (counts_as: failure) or
1713
+ ``decided_not_counted`` when a decision names the cell; ``confirmed``
1714
+ for its own confirmed verdict or one inherited from a compile-time
1715
+ defect signature (an inherited verdict of any other kind reads
1716
+ ``unchecked``); else the verdict, or ``ungated``."""
1717
+ if decision:
1718
+ return "decided" if decision.get("counts_as") == "failure" else \
1719
+ "decided_not_counted"
1720
+ if rec is None:
1721
+ return "ungated"
1722
+ v = rec.get("verdict") or "ungated"
1723
+ if v == "confirmed" and rec.get("inherited"):
1724
+ from offload.engine.gate import COMPILE_CLASSES
1725
+ parts = str(rec.get("signature", "")).split("|")
1726
+ return "confirmed" if len(parts) > 1 and \
1727
+ parts[1] in COMPILE_CLASSES else "unchecked"
1728
+ return v
1729
+
1730
+
1731
+ def runtime_ruling(r: Row):
1732
+ """The recorded ruling (catalog/rulings.yaml, `decides` naming outcome
1733
+ classes) that decides the cell, or None: e.g.
1734
+ RUL-HOST-LIBATOMIC (our_bug) or RUL-MAPPED-FLAG-NOT-COPIED-BACK (a
1735
+ fell-back-to-host cell that ran on the device: a finding's wrong
1736
+ answer)."""
1737
+ import re as _re
1738
+ from fnmatch import fnmatchcase
1739
+ from offload.engine.confirm import registry as REG
1740
+ from offload.engine.confirm.facts import family as _family
1741
+ for e in REG.entries().values():
1742
+ d = e.decides or {}
1743
+ outs = d.get("outcome")
1744
+ if not d.get("verdict") or not isinstance(outs, list):
1745
+ continue
1746
+ if r.outcome not in outs:
1747
+ continue
1748
+ if not any(fnmatchcase(r.program_id, p)
1749
+ for p in e.applies.get("programs", ())):
1750
+ continue
1751
+ if d.get("families") and _family(r.toolchain) not in d["families"]:
1752
+ continue
1753
+ if d.get("toolchains") and r.toolchain not in d["toolchains"]:
1754
+ continue
1755
+ if d.get("reason") and not _re.search(d["reason"], r.reason or ""):
1756
+ continue
1757
+ return e
1758
+ return None
1759
+
1760
+
1761
+ def _ours(r: Row) -> bool:
1762
+ """A recorded ruling says the cell's failure is our build's (verdict
1763
+ our_bug; the withdrawn complex-reduction link)."""
1764
+ e = runtime_ruling(r)
1765
+ return e is not None and e.decides.get("verdict") == "our_bug"
1766
+
1767
+
1768
+ # the GCC lane gates its own rows into verdict files of its own (tools/campaigns/gcc_lane.py,
1769
+ # one per host it ran on), keyed by cell only
1770
+ LANE_VERDICTS = ROOT / "results" / "gcc"
1771
+
1772
+
1773
+ def _lane_index(paths) -> dict:
1774
+ """(kind, ident, toolchain, row key) -> the lane's verdict record of a catalog cell."""
1775
+ out = {}
1776
+ for p in paths:
1777
+ for ln in Path(p).read_text().splitlines() if Path(p).is_file() else ():
1778
+ if ln.strip():
1779
+ d = json.loads(ln)
1780
+ kind, ident, tc, x = d["cell"].split("|")
1781
+ out[(kind, ident, tc, int(x))] = d
1782
+ return out
1783
+
1784
+
1785
+ def annotate_gate(table: list[Row], verdicts: Path | None = None,
1786
+ decisions: Path | None = None, lanes=None) -> list[Row]:
1787
+ """The rows with the gate column set on every failing legal cell
1788
+ (_failure of its label); the catalog and the widened catalog are one
1789
+ verdict kind (offload.engine.gate._case_index). `lanes`: the GCC lane's
1790
+ verdict files (default: those under LANE_VERDICTS when `verdicts` is the
1791
+ gate's store); the gate's store wins a cell both hold."""
1792
+ from offload.engine import gate as G
1793
+ if lanes is None:
1794
+ lanes = sorted(LANE_VERDICTS.glob("gate*/verdicts.jsonl")) if verdicts is None else []
1795
+ idx = {**_lane_index(lanes), **G._case_index(verdicts)}
1796
+ dec = G.load_decisions(decisions)
1797
+ out = []
1798
+ for r in table:
1799
+ if not r.legal or not _failure(r.label):
1800
+ out.append(replace(r, gate="") if r.gate else r)
1801
+ continue
1802
+ if _ours(r):
1803
+ out.append(replace(r, gate="ours"))
1804
+ continue
1805
+ rec = idx.get(("catalog", r.program_id, r.toolchain, int(r.xnack)))
1806
+ d = dec.get(rec["cell"]) if rec else None
1807
+ out.append(replace(r, gate=gate_status(rec, d)))
1808
+ return out
1809
+
1810
+
1811
+ def confirmed_failure(r: Row) -> bool:
1812
+ """Whether a support-table cell counts as a compiler failure (the ruling on): a failing legal cell whose gate
1813
+ status is ``confirmed`` (its own confirmed rerun, or a confirmed
1814
+ verdict inherited from a compile-time signature) or ``decided`` (a
1815
+ Decision with counts_as: failure).
1816
+
1817
+ It differs from offload.engine.gate.counts / counts_as_failure on purpose:
1818
+ those answer "does the row keep counting in a campaign's counts" and
1819
+ keep an ``unchecked`` row (not rerun) and a row the gate has not seen
1820
+ at their previous outcome; the support tables answer "is this failure
1821
+ confirmed", so an unchecked, ungated, nondeterministic or otherwise
1822
+ unconfirmed cell is shown apart and not counted."""
1823
+ return r.legal and _failure(r.label) and r.gate in COUNTED_STATES
1824
+
1825
+
1826
+ def gate_section(table: list[Row]) -> list[str]:
1827
+ """The Markdown section of the gate column: failing legal cells by
1828
+ gate status per column, and the failing cells that do not count."""
1829
+ bad = [r for r in table if r.legal and _failure(r.label)]
1830
+ order = columns()
1831
+ cols = sorted({(r.toolchain, r.xnack) for r in table},
1832
+ key=lambda c: (order.index(c) if c in order else
1833
+ len(order), c[0], -c[1]))
1834
+ head = " | ".join(f"{TC_NAME.get(t, t)} X={x}" if t not in NV else
1835
+ f"{TC_NAME.get(t, t)} {nv_mode_name(t, x)}"
1836
+ for t, x in cols)
1837
+ out = ["## Reproduction gate", "",
1838
+ "Each failing legal cell (a counted rejection, a wrong answer, a "
1839
+ "crash, a host fallback, a failed probe) carries the "
1840
+ "reproduction gate's status (offload.engine.gate, "
1841
+ "results/gate/verdicts.jsonl, and the decisions of "
1842
+ "catalog/gate_queue.yaml): **confirmed** (its own rerun verdict, "
1843
+ "or one inherited from a compile-time defect signature), "
1844
+ "**decided** (a decision; it counts when the decision "
1845
+ "says counts_as: failure), **unchecked** (the gate did not rerun "
1846
+ "it), **nondeterministic**, **ours** (a recorded ruling says the "
1847
+ "failure is our build's), the other verdicts, or **ungated** (no "
1848
+ "verdict yet). A cell counts as a compiler failure only when "
1849
+ "confirmed or decided as counting (report.confirmed_failure; the "
1850
+ "campaign counts of offload.engine.gate.counts keep unchecked rows at "
1851
+ "their previous outcome, a different question); every other "
1852
+ "failing cell is listed apart below, and only counted cells (and "
1853
+ "ungated ones) contradict a support claim. The CSV's "
1854
+ "last column, `gate`, has the status of every failing cell.", "",
1855
+ f"| gate | {head} |", "|---|" + "---|" * len(cols)]
1856
+ states = [g for g in GATE_STATES if any(r.gate == g for r in bad)]
1857
+ for g in states:
1858
+ out.append(f"| {GATE_TEXT[g]} | " + " | ".join(
1859
+ str(sum(1 for r in bad if r.gate == g and
1860
+ (r.toolchain, r.xnack) == c)) for c in cols) + " |")
1861
+ out.append("| failing cells | " + " | ".join(
1862
+ str(sum(1 for r in bad if (r.toolchain, r.xnack) == c))
1863
+ for c in cols) + " |")
1864
+ ruled = [(r, e) for r in bad if (e := runtime_ruling(r)) is not None
1865
+ and e.decides.get("verdict") != "our_bug"]
1866
+ if ruled:
1867
+ out += ["", "### Cells a recorded ruling reads otherwise", "",
1868
+ "Counted as above; the label is the harness's outcome, the "
1869
+ "ruling (catalog/rulings.yaml) says what the cell is (review "
1870
+ "M10: a ran_on_host=1 cell whose region ran on the device "
1871
+ "and whose map(tofrom) flag was not copied back is a wrong "
1872
+ "answer, not a host fallback).", "",
1873
+ "| version | program | toolchain | X | report | ruling |",
1874
+ "|---|---|---|---|---|---|"]
1875
+ out += [f"| {_vstr(r.version)} | {r.program_id} | "
1876
+ f"{TC_NAME.get(r.toolchain, r.toolchain)} | {r.xnack} | "
1877
+ f"{_md(_clip(r.label, 60))} | {e.id} |" for r, e in ruled]
1878
+ apart = [r for r in bad if not confirmed_failure(r)]
1879
+ out += ["", "### Not counted: failing cells without a confirmed verdict "
1880
+ "or a counting decision",
1881
+ ""]
1882
+ if apart:
1883
+ out += ["| version | program | toolchain | X | report | gate |",
1884
+ "|---|---|---|---|---|---|"]
1885
+ out += [f"| {_vstr(r.version)} | {r.program_id} | "
1886
+ f"{TC_NAME.get(r.toolchain, r.toolchain)} | {r.xnack} | "
1887
+ f"{_md(_clip(r.label, 90))} | {r.gate} |" for r in apart]
1888
+ else:
1889
+ out.append("None.")
1890
+ out.append("")
1891
+ return out
1892
+
1893
+
1894
+ CSV_FIELDS = ("version", "program_id", "atom", "legal", "violates",
1895
+ "toolchain", "xnack", "outcome", "label", "flaky", "evidence",
1896
+ "audit", "needs", "thp", "citation", "reason", "mode",
1897
+ "copy_label", "host", "suite_narrow", "basis",
1898
+ # The program's language (f90 | c); last, so the
1899
+ # earlier columns keep their positions
1900
+ "lang",
1901
+ # Device | host+device (a pattern program, not the
1902
+ # atom's headline)
1903
+ "scope",
1904
+ # Headline legal rows: the portable verdict
1905
+ # (PORTABLE) and the class of a mode difference
1906
+ "portable", "mode_class",
1907
+ # Presence checks at HSA_XNACK=1
1908
+ "presence_x1",
1909
+ # The row's GPU arch (gfx942 | gfx90a) and, on
1910
+ # headline rows, the class of a gfx90a difference
1911
+ "arch", "arch_class",
1912
+ # Failing legal cells: the reproduction gate's
1913
+ # status (counted as a compiler failure only when confirmed)
1914
+ "gate")
1915
+
1916
+
1917
+ def _suite_text(suite: dict | None, atom: str, tc: str) -> str:
1918
+ """'n pass / n fail' of the narrow omp_vv tests of an atom, '' when
1919
+ none."""
1920
+ v = (suite or {}).get((atom, tc))
1921
+ return f"{v[0]} pass / {v[1]} fail" if v and sum(v) else ""
1922
+
1923
+
1924
+ def _paths_out(text: str) -> str:
1925
+ """A compiler message with its install path named by the install ($NVHPC_26_9), not by where it is."""
1926
+ from offload.engine.site.matrix import B
1927
+ return B.scrub(text)
1928
+
1929
+
1930
+ def render_csv(table: list[Row], suite: dict | None = None,
1931
+ meta: dict | None = None) -> str:
1932
+ """One CSV row per cell. `mode`: headline (HSA_XNACK=1) or copy;
1933
+ `copy_label` (headline rows): the same program's copy-mode label or
1934
+ 'not run'; `host`: the node per the run metadata; `suite_narrow`: the
1935
+ omp_vv second source for the atom and toolchain."""
1936
+ host = program_hosts(meta or {})
1937
+ g90_host = g90_hosts(meta or {})
1938
+ buf = io.StringIO()
1939
+ w = csv.writer(buf, lineterminator="\n")
1940
+ w.writerow(CSV_FIELDS)
1941
+ for r in table:
1942
+ cite = (atom_citation(r.atom, r.version)[0] if r.legal
1943
+ else rule_citation(r.violates)[0])
1944
+ arch = arch_of(r.toolchain)
1945
+ twin = r.toolchain in TWINS
1946
+ mode = _reg().twin_tag(r.toolchain) if twin else \
1947
+ ("headline" if r.xnack == HEADLINE else "copy") \
1948
+ if arch == "gfx942" else arch
1949
+ nvh = r.toolchain in NV and r.xnack == nv_modes(r.toolchain)[0]
1950
+ if r.toolchain in NV: # E.g. "sm_80 separate"
1951
+ mode = f"{arch} {nv_mode_name(r.toolchain, r.xnack)}"
1952
+ w.writerow([r.version, r.program_id, r.atom, int(r.legal),
1953
+ r.violates or "", r.toolchain, r.xnack, r.outcome,
1954
+ r.label, int(r.flaky), r.evidence, r.audit,
1955
+ " ".join(r.needs), r.thp, cite, _clip(_paths_out(r.reason), 300),
1956
+ mode, r.copy_label if r.xnack == HEADLINE and
1957
+ r.toolchain not in NV or nvh else "",
1958
+ (host(r.program_id, r.xnack) if arch == "gfx942" and not twin
1959
+ else g90_host(r.program_id) if r.toolchain in G90
1960
+ else "") or "",
1961
+ _suite_text(suite, r.atom, r.toolchain) if r.legal
1962
+ else "", r.basis, r.lang, r.scope, r.portable,
1963
+ r.mode_class, r.presence_x1, arch, r.arch_class,
1964
+ r.gate])
1965
+ return buf.getvalue()
1966
+
1967
+
1968
+ def program_hosts(meta: dict):
1969
+ """host(program_id, xnack) -> the node its row ran on per the run
1970
+ metadata (the mode's run, then its reruns in order), None if not
1971
+ recorded."""
1972
+ table: dict[tuple, str] = {}
1973
+ base: dict[int, str] = {}
1974
+ for x in MODES:
1975
+ m = meta.get(x) or {}
1976
+ if m.get("host"):
1977
+ base[x] = m["host"]
1978
+ for rr in m.get("reruns", []):
1979
+ for p in rr.get("programs", []):
1980
+ table[(p, x)] = rr.get("host")
1981
+ return lambda pid, x: table.get((pid, x), base.get(x))
1982
+
1983
+
1984
+ def g90_hosts(meta: dict):
1985
+ """host(program_id) -> the Tioga node of a program's gfx90a row (Task
1986
+ 30: the replace runs recorded in meta["g90"]), None if not
1987
+ recorded."""
1988
+ table: dict[str, str] = {}
1989
+ for rr in (meta.get("g90") or {}).get("reruns", []):
1990
+ for p in rr.get("programs", []):
1991
+ table[p] = rr.get("host")
1992
+ return table.get
1993
+
1994
+
1995
+ def _cells(rows: list[Row]) -> dict[tuple, Row]:
1996
+ return {(r.toolchain, r.xnack): r for r in rows}
1997
+
1998
+
1999
+ def _mode_key(row: Row) -> tuple:
2000
+ """What must differ between the two modes for a dagger: the outcome and
2001
+ whether it was a crash, not the label (copy_mode_only labels differ by
2002
+ design)."""
2003
+ return (row.outcome, row.reason.startswith("crash:"))
2004
+
2005
+
2006
+ def _mode_cells(rows: list[Row], host=None) -> list[str]:
2007
+ """Cell texts in column order (per toolchain: headline, copy mode). A
2008
+ dagger marks a toolchain whose two modes differ and whose two rows ran
2009
+ on different or unrecorded nodes (`host(program_id, xnack)`); a
2010
+ copy_mode_only headline cell with a copy-mode row carries COPY_MARK."""
2011
+ host = host or (lambda pid, x: None)
2012
+ cells = _cells(rows)
2013
+ out = []
2014
+ for tc in TOOLCHAINS:
2015
+ both = [cells.get((tc, x)) for x in MODES] if tc in MI300A else []
2016
+ differ = bool(both) and all(both) and _mode_key(both[0]) != _mode_key(both[1])
2017
+ if differ:
2018
+ h = [host(r.program_id, r.xnack) for r in both]
2019
+ differ = None in h or h[0] != h[1]
2020
+ for x, r in zip(MODES, both):
2021
+ if r is None:
2022
+ out.append(NOT_RUN if x == COPY else "n/a")
2023
+ continue
2024
+ mark = (f" {COPY_MARK}" if x == HEADLINE and r.legal
2025
+ and r.evidence == "copy_mode_only" and both[1]
2026
+ and r.outcome not in _COMPILE_TIME else "")
2027
+ out.append(_md(r.label) + mark + (" †" if differ else ""))
2028
+ for w in twins_of(tc): # The gfx90a, MI300X and MI350X cells
2029
+ g = cells.get((w, COPY))
2030
+ out.append(_md(g.label) if g else NOT_RUN)
2031
+ for tc in NV: # One cell per mode of its machine
2032
+ for x in nv_modes(tc):
2033
+ r = cells.get((tc, x))
2034
+ out.append(_md(r.label) if r else NOT_RUN)
2035
+ return out
2036
+
2037
+
2038
+ def _head_modes() -> str:
2039
+ cols = [f"{TC_NAME[tc]} {nv_mode_name(tc, x)} (A100)" if tc in NV
2040
+ else f"{TC_NAME[tc]} (X=1)" if x == HEADLINE
2041
+ else f"{TC_NAME[tc]} (X=0)" if tc in TWINS
2042
+ else f"{TC_NAME[tc]} copy mode (X=0)"
2043
+ for tc, x in columns()]
2044
+ return " | ".join(cols)
2045
+
2046
+
2047
+ COPY_MARK_TEXT = (f"{COPY_MARK} = copy_mode_only: the check separates the "
2048
+ "construct from a no-op only in copy mode, so the cell's "
2049
+ "real check is its copy-mode column.")
2050
+ DAGGER_TEXT = ("† = the two modes differ for that toolchain and their rows "
2051
+ "ran on different (or unrecorded) nodes, so the node may "
2052
+ "explain the difference as well as the memory mode.")
2053
+
2054
+
2055
+ def _versions_text(table: list[Row]) -> str:
2056
+ """'5.2 and 6.0' (or '5.0, 5.2 and 6.0') for the versions in a table."""
2057
+ vs = [_vstr(v) for v in sorted({r.version for r in table})] or [
2058
+ "5.2", "6.0"]
2059
+ return vs[0] if len(vs) == 1 else ", ".join(vs[:-1]) + " and " + vs[-1]
2060
+
2061
+
2062
+ def _provisional_section(table: list[Row]) -> list[str]:
2063
+ model = table_model(table)
2064
+ CLAUSES, RULES, OVERRIDES, LINK = _paths(model)
2065
+ clauses = _yaml(CLAUSES).get("clauses", {})
2066
+ rules = _yaml(RULES).get("rules", [])
2067
+ ov = _yaml(OVERRIDES) or {}
2068
+ ov_lists = {k: v for k, v in ov.items() if isinstance(v, list)}
2069
+ n_ov = sum(len(v) for v in ov_lists.values())
2070
+ st = Counter(e.get("status") for e in clauses.values())
2071
+ rs = Counter(r.get("status") for r in rules)
2072
+ os_ = Counter(e.get("status") for v in ov_lists.values() for e in v)
2073
+ ov_clauses = sorted({e["clause"] for e in ov.get("clause_rules", [])})
2074
+ ov_dirs = sorted({e["directive"] for k in ("directive_versions",
2075
+ "directive_spellings")
2076
+ for e in ov.get(k, [])})
2077
+ atoms = sorted({r.atom for r in table if r.legal})
2078
+ hit_c = [a for a in atoms if a.startswith(f"{model}.clause.")
2079
+ and a.split(".")[2] in ov_clauses]
2080
+ hit_d = [a for a in atoms if a.startswith(f"{model}.directive.")
2081
+ and a.split(".")[2].replace("_", " ") in ov_dirs]
2082
+ illegal = sorted({r.violates for r in table if not r.legal and r.violates})
2083
+ vers = _versions_text(table)
2084
+ return [
2085
+ "## Results that depend on provisional rules",
2086
+ "",
2087
+ "Nothing in the catalog has been reviewed by the maintainer yet, so every "
2088
+ "result depends on provisional data:",
2089
+ "",
2090
+ f"- **Every legal cell** depends on its atom's clause-grammar entry "
2091
+ f"([clauses.yaml]({LINK['clauses']}): "
2092
+ f"{dict(st)} of {len(clauses)} clauses) and on the generator's "
2093
+ "reading of it.",
2094
+ f"- **Every illegal-variant cell** depends on the one rule it violates "
2095
+ f"([rules.yaml]({LINK['rules']}): {dict(rs)} of {len(rules)} rules; "
2096
+ f"{len(illegal)} rule versions exercised here).",
2097
+ f"- **Spec overrides** of the TableGen skeleton "
2098
+ f"(catalog/{model}/spec_overrides.yaml: {dict(os_)} of {n_ov}) "
2099
+ "decide whether a clause is allowed on a directive"
2100
+ + (f" at {vers.replace(' and ', ' or ')}" if model == "omp"
2101
+ else "") + ". Atoms whose "
2102
+ f"clause has an override ({len(hit_c)}): "
2103
+ + ", ".join(f"`{a.removeprefix(model + '.')}`" for a in hit_c) + ". "
2104
+ f"Directive atoms whose version or spelling is an override "
2105
+ f"({len(hit_d)}): "
2106
+ + (", ".join(f"`{a.removeprefix(model + '.')}`" for a in hit_d)
2107
+ or "none")
2108
+ + ".",
2109
+ "- **Every vendor claim** in "
2110
+ f"[claims.yaml]({LINK['claims']}) is provisional: the mapping from a "
2111
+ "vendor's prose to an atom is this project's reading.",
2112
+ "",
2113
+ ]
2114
+
2115
+
2116
+ def _baseline_section(baseline: list[dict], meta: dict | None = None,
2117
+ c_lang: bool = False) -> list[str]:
2118
+ if not baseline:
2119
+ return ["## Corpus baseline", "", "No baseline results given.", ""]
2120
+ if c_lang: # The omp_vv C tests
2121
+ cnt = Counter((b["toolchain"], b["xnack"], b["status"],
2122
+ b.get("stage", "")) for b in baseline)
2123
+ files = len({(b.get("repo"), b.get("path")) for b in baseline})
2124
+ lines = [
2125
+ "## Corpus baseline (omp_vv C target tests)", "",
2126
+ f"{len(baseline)} results ({files} files x toolchain x memory "
2127
+ f"mode run). {_baseline_modes(baseline, meta or {})} Each test "
2128
+ "is compiled with the compiler's default OpenMP version (5.1) "
2129
+ "when its spec directory is 4.5, 5.0 or 5.1, with "
2130
+ "-fopenmp-version= its directory for 5.2 and 6.0, and with the "
2131
+ "suite's -I ompvv -lm, run once under "
2132
+ "OMP_TARGET_OFFLOAD=mandatory. Pass criterion: exit code 0; the "
2133
+ "tests are **unaudited** (their self-checks were not "
2134
+ "mutation-tested).", "",
2135
+ "| toolchain | HSA_XNACK | pass | fail (compile) | fail (run) | "
2136
+ "harness error |", "|---|---|---|---|---|---|"]
2137
+ for tc, x in columns():
2138
+ if not any(b["toolchain"] == tc and b["xnack"] == x
2139
+ for b in baseline):
2140
+ continue
2141
+ lines.append(
2142
+ f"| {TC_NAME[tc]} | {x} | {cnt[(tc, x, 'pass', 'run')]} | "
2143
+ f"{cnt[(tc, x, 'fail', 'compile')]} | "
2144
+ f"{cnt[(tc, x, 'fail', 'run')]} | "
2145
+ f"{sum(v for k, v in cnt.items() if k[:3] == (tc, x, 'harness_error'))} |")
2146
+ return lines + [""] + _baseline_mode_flags(baseline)
2147
+ cnt = Counter((b["toolchain"], b["xnack"], b["status"], b.get("stage", ""))
2148
+ for b in baseline)
2149
+ host = [b for b in baseline if b["status"] == "pass"
2150
+ and "passed on the host" in b.get("stdout", "").lower()]
2151
+ hc = Counter((b["toolchain"], b["xnack"]) for b in host)
2152
+ files = len({(b.get("repo"), b.get("path")) for b in baseline})
2153
+ lines = [
2154
+ "## Corpus baseline (omp_vv Fortran target tests)",
2155
+ "",
2156
+ f"{len(baseline)} results ({files} files x toolchain x memory mode "
2157
+ f"run). {_baseline_modes(baseline, meta or {})} "
2158
+ "Pass criterion: exit code 0 (Global Constraints); these tests are "
2159
+ "**unaudited**: their self-checks were not mutation-tested, so a pass "
2160
+ "says the program compiled and exited 0, not that the feature works.",
2161
+ "",
2162
+ "| toolchain | HSA_XNACK | pass | fail (compile) | fail (run) | "
2163
+ "of which pass prints 'Test passed on the host' [^host] |",
2164
+ "|---|---|---|---|---|---|",
2165
+ ]
2166
+ for tc, x in columns():
2167
+ lines.append(
2168
+ f"| {TC_NAME[tc]} | {x} | {cnt[(tc, x, 'pass', 'run')]} | "
2169
+ f"{cnt[(tc, x, 'fail', 'compile')]} | "
2170
+ f"{cnt[(tc, x, 'fail', 'run')]} | {hc[(tc, x)]} |")
2171
+ lines += [
2172
+ "",
2173
+ f"[^host]: {len(host)} baseline passes print \"Test passed on the "
2174
+ "host\". The omp_vv module (ompvv/ompvv.F90) prints \"host\" when its "
2175
+ "flag ompvv_isHost is true: the flag starts .true. and only the "
2176
+ "test's offloading probe (a target region calling "
2177
+ "omp_is_initial_device) sets it, so the message means either that the "
2178
+ "probe ran on the initial device or that the test never called the "
2179
+ "probe; every run set OMP_TARGET_OFFLOAD=mandatory. Exit code 0 counts "
2180
+ "them as passes under the baseline rule; they are not evidence of "
2181
+ "device execution. "
2182
+ "Files: "
2183
+ + ", ".join(sorted({b['path'].rsplit('/', 1)[-1] for b in host}))
2184
+ + ".",
2185
+ "",
2186
+ ]
2187
+ return lines + _baseline_mode_flags(baseline)
2188
+
2189
+
2190
+ def _baseline_mode_flags(baseline: list[dict]) -> list[str]:
2191
+ """Omp_vv tests whose status differs between the two memory
2192
+ modes on a toolchain. The tests are untrusted (not mutation-tested), so
2193
+ these are flags for review, never findings."""
2194
+ by: dict[tuple, dict] = defaultdict(dict)
2195
+ for b in baseline:
2196
+ if arch_of(b["toolchain"]) != "gfx942":
2197
+ continue # _baseline_g90_flags
2198
+ by[(b["path"], b["toolchain"])][b["xnack"]] = b
2199
+ diff = [(p, t, m) for (p, t), m in sorted(by.items())
2200
+ if len(m) == 2 and m[0]["status"] != m[1]["status"]]
2201
+ out = ["### omp_vv tests whose memory modes differ (flags)", "",
2202
+ "omp_vv tests are untrusted (their checks were not "
2203
+ "mutation-tested): a test that passes in one mode only is "
2204
+ "flagged for review, never reported as a compiler finding.", ""]
2205
+ if not diff:
2206
+ return out + ["None.", ""] + _baseline_g90_flags(baseline)
2207
+ c = Counter((t, m[1]["status"] == "pass") for _, t, m in diff)
2208
+ out += ["| toolchain | passes at X=1 only | passes at X=0 only |",
2209
+ "|---|---|---|"]
2210
+ for t in TOOLCHAINS:
2211
+ if c[(t, True)] or c[(t, False)]:
2212
+ out.append(f"| {TC_NAME[t]} | {c[(t, True)]} | "
2213
+ f"{c[(t, False)]} |")
2214
+ out += ["", "| test | toolchain | X=1 | X=0 |", "|---|---|---|---|"]
2215
+ for p, t, m in diff:
2216
+ def cell(b):
2217
+ return _md(_clip(f"{b['status']} ({b.get('stage', '')}: "
2218
+ f"{b.get('reason', '')})", 120))
2219
+ out.append(f"| {p.removeprefix('tests/')} | "
2220
+ f"{TC_NAME.get(t, t)} | {cell(m[1])} | {cell(m[0])} |")
2221
+ return out + [""] + _baseline_g90_flags(baseline)
2222
+
2223
+
2224
+ def _baseline_g90_flags(baseline: list[dict]) -> list[str]:
2225
+ """Omp_vv tests whose gfx90a status differs from the same
2226
+ compiler's MI300A copy-mode (HSA_XNACK=0) status; flags for review,
2227
+ never findings (the tests are untrusted)."""
2228
+ by: dict[tuple, dict] = defaultdict(dict)
2229
+ for b in baseline:
2230
+ by[(b["path"], base_name(b["toolchain"]))][
2231
+ arch_of(b["toolchain"])] = b
2232
+ pairs = [(p, t, m) for (p, t), m in sorted(by.items())
2233
+ if "gfx90a" in m and "gfx942" in m]
2234
+ if not pairs:
2235
+ return []
2236
+ diff = [(p, t, m) for p, t, m in pairs
2237
+ if m["gfx90a"]["status"] != m["gfx942"]["status"]]
2238
+ out = ["### omp_vv tests whose gfx90a result differs from MI300A copy "
2239
+ "mode (flags)", "",
2240
+ f"{len(diff)} of {len(pairs)} (test, compiler) pairs differ "
2241
+ "between MI300A at HSA_XNACK=0 and gfx90a (HSA_XNACK=0). The "
2242
+ "tests are untrusted: flags for review, never findings.", ""]
2243
+ if not diff:
2244
+ return out + ["None.", ""]
2245
+
2246
+ def cell(b):
2247
+ return _md(_clip(f"{b['status']} ({b.get('stage', '')}: "
2248
+ f"{b.get('reason', '')})", 120))
2249
+ out += ["| test | compiler | MI300A X=0 | gfx90a |", "|---|---|---|---|"]
2250
+ for p, t, m in diff:
2251
+ out.append(f"| {p.removeprefix('tests/')} | {TC_NAME.get(t, t)} | "
2252
+ f"{cell(m['gfx942'])} | {cell(m['gfx90a'])} |")
2253
+ return out + [""]
2254
+
2255
+
2256
+ def _baseline_modes(baseline: list[dict], meta: dict) -> str:
2257
+ """Which tests ran in which mode, and on which nodes."""
2258
+ by = defaultdict(set)
2259
+ for b in baseline:
2260
+ by[b["xnack"]].add(b["path"])
2261
+ if by[COPY] == by[HEADLINE]:
2262
+ text = "Every test ran in both modes"
2263
+ else:
2264
+ text = (f"Every test ran at HSA_XNACK=1 ({len(by[HEADLINE])} files); "
2265
+ f"copy mode ran only the {len(by[COPY])} tests whose result "
2266
+ "depends on copy semantics (offload.engine.ompvv.copy_semantics)")
2267
+ nodes = []
2268
+ for x in MODES:
2269
+ m = meta.get(x) or {}
2270
+ if m.get("baseline_rows"):
2271
+ nodes.append(f"HSA_XNACK={x} with the matrix run on "
2272
+ f"{m.get('host', '?')}")
2273
+ for rr in (meta.get(f"baseline{x}") or {}).get("reruns", []):
2274
+ nodes.append(f"HSA_XNACK={x} rows of "
2275
+ f"{len(rr.get('baseline_paths', []))} tests rerun on "
2276
+ f"{rr.get('host', '?')} ({rr.get('started', '?')})")
2277
+ return text + (" (" + "; ".join(nodes) + ")." if nodes else
2278
+ "; nodes not recorded.")
2279
+
2280
+
2281
+ def _claims_section(table: list[Row], mismatches: list[Mismatch],
2282
+ claims: list[Claim]) -> list[str]:
2283
+ groups = claim_groups(table, claims)
2284
+ sources: dict[tuple, Claim] = {}
2285
+ for c in claims: # One row per document edition
2286
+ sources.setdefault((c.source, c.toolchain), c)
2287
+ lines = [
2288
+ "## Vendor claims versus measurements",
2289
+ "",
2290
+ "All claims are **provisional** and cited in "
2291
+ f"[claims.yaml]({LINK['claims']}). A claim covers an atom by name or "
2292
+ "pattern; a release-level (general) claim applies only where no "
2293
+ "specific claim for that toolchain covers the atom. Comparison rules: "
2294
+ "`supported` is contradicted by rejected, ignored, fell back, wrong "
2295
+ "answer or crash; `unsupported` by correct on device (or, for a "
2296
+ "compile-error claim, by anything but rejection); `partial` only when "
2297
+ "the vendor says the form is accepted and it is rejected. Cells that "
2298
+ "cannot separate the construct (no oracle: hint only, "
2299
+ "implementation-defined, equivalent default; legality; unverified; "
2300
+ "unobservable) never contradict a claim, except that a crash of such "
2301
+ "an atom does (a crash needs no oracle), and they never count as "
2302
+ "support; zero-copy programs in copy mode and illegal "
2303
+ "variants are not compared. Both memory modes are compared where both "
2304
+ "ran.",
2305
+ "",
2306
+ "### Sources",
2307
+ "",
2308
+ "| source | toolchain | language of the claim | URL | version note |",
2309
+ "|---|---|---|---|---|",
2310
+ ]
2311
+ for (s, _), c in sources.items():
2312
+ lines.append(f"| {s} | {TC_NAME.get(c.toolchain, c.toolchain)} | "
2313
+ f"{c.language} | <{c.url}> | {_md(c.version_note)} |")
2314
+ n_groups = len(groups)
2315
+ confirmed = [m for m in mismatches if not m.oracle_unaudited]
2316
+ unaud = [m for m in mismatches if m.oracle_unaudited]
2317
+ lines += [
2318
+ "",
2319
+ f"{len(claims)} claim entries; {n_groups} (claim, atom, version, "
2320
+ f"toolchain) comparisons; {len(mismatches)} mismatches "
2321
+ f"({len(confirmed)} from a compile result, a crash, or an audited "
2322
+ "oracle, "
2323
+ f"{len(unaud)} from a run-time result of an oracle-unaudited atom). "
2324
+ "A rejection can come from another feature the program also uses; "
2325
+ "the harness reason column shows the compiler's message.",
2326
+ "",
2327
+ ]
2328
+
2329
+ def table_of(ms: list[Mismatch]) -> list[str]:
2330
+ out = ["| toolchain | atom | version | claimed [source] | measured | "
2331
+ "harness reason (first contradicting cell) | vendor text |",
2332
+ "|---|---|---|---|---|---|---|"]
2333
+ for m in ms:
2334
+ out.append(
2335
+ f"| {TC_NAME.get(m.toolchain, m.toolchain)} | "
2336
+ f"`{m.atom.removeprefix('omp.')}` | {_vstr(m.version)} | "
2337
+ f"{_md(m.claimed)}{' (general)' if m.general else ''}"
2338
+ f"{' (C/C++ table)' if m.language == 'C/C++' and m.lang != 'c' else ''} | "
2339
+ f"{_md(m.measured)} | {_md(_clip(m.reason, 110))} | "
2340
+ f"[{_md(_clip(m.quote, 90))}]({m.url}) |")
2341
+ return out
2342
+
2343
+ caveats = sorted({m.confound for m in mismatches if m.confound})
2344
+ lines += ["### Mismatches", ""] + (table_of(confirmed) if confirmed
2345
+ else ["None."])
2346
+ if caveats:
2347
+ lines += ["", "‡ Confounded program (generator follow-up): "
2348
+ + "; ".join(caveats) + "."]
2349
+ lines += ["", "### Mismatches on oracle-unaudited atoms (not confirmed)",
2350
+ "",
2351
+ "Run-time outcomes of atoms whose oracle is unaudited (no "
2352
+ "toolchain passes the original program). The measured outcome "
2353
+ "stands, but these are not confirmed failures.", ""]
2354
+ lines += table_of(unaud) if unaud else ["None."]
2355
+ lines.append("")
2356
+ return lines
2357
+
2358
+
2359
+ def _rerun_winners(modes: dict) -> str:
2360
+ """Which rerun produced the final rows of a program rerun more than
2361
+ once in one mode (the later run replaced the earlier rows)."""
2362
+ parts = []
2363
+ for x in MODES:
2364
+ last: dict[str, str] = {}
2365
+ seen: Counter = Counter()
2366
+ for rr in modes.get(x, {}).get("reruns", []):
2367
+ for p in rr.get("programs", []):
2368
+ seen[p] += 1
2369
+ last[p] = rr.get("host", "?")
2370
+ multi = sorted(p for p, n in seen.items() if n > 1)
2371
+ if multi:
2372
+ parts.append(f"HSA_XNACK={x}: " + ", ".join(
2373
+ f"`{p}`: {last[p]}" for p in multi))
2374
+ if not parts:
2375
+ return ""
2376
+ return ("- Programs rerun more than once in one mode: their rows come "
2377
+ "from the last rerun, " + "; ".join(parts) + ".")
2378
+
2379
+
2380
+ def _stale_audit_note(legal: list[Row]) -> str:
2381
+ """One line naming the atoms the audit calls unaudited although a
2382
+ toolchain passes them in the matrix."""
2383
+ stale = sorted({(r.version, r.atom) for r in legal
2384
+ if r.audit.startswith("unaudited")
2385
+ and r.outcome == "correct_on_device"})
2386
+ if not stale:
2387
+ return ("- Audit dispositions come from the committed audit run; no "
2388
+ "unaudited atom passes in the matrix.")
2389
+ names = ", ".join(f"`{a.split('.', 1)[1]}` ({_vstr(v)})"
2390
+ for v, a in stale)
2391
+ if all(a.startswith("acc.") for _, a in stale):
2392
+ return ("- **The OpenACC audit runs on CCE 19 only**: for these "
2393
+ "atoms CCE 19 fails the original while another toolchain "
2394
+ "passes it in the matrix, so 'oracle unaudited' does not "
2395
+ "mean that no compiler runs the program: " + names + ".")
2396
+ return ("- **Audit dispositions predate the explicit -fopenmp-version=60 "
2397
+ "run** of AFAR's 6.0 programs, in which AFAR rejected the "
2398
+ "originals. For these atoms a toolchain passes the original in "
2399
+ "the matrix, so 'oracle unaudited' may not apply (re-audit is a "
2400
+ f"follow-up): {names}.")
2401
+
2402
+
2403
+ def _node_counts(table: list[Row], host) -> tuple[int, int, int]:
2404
+ """(same node, different nodes, unrecorded) over the (program,
2405
+ toolchain) pairs that have both modes."""
2406
+ pairs = defaultdict(dict)
2407
+ for r in table:
2408
+ if r.toolchain in NVIDIA_TOOLCHAINS: # AMD modes only
2409
+ continue
2410
+ pairs[(r.program_id, r.toolchain)][r.xnack] = r
2411
+ same = diff = unknown = 0
2412
+ for (pid, _), m in pairs.items():
2413
+ if len(m) < 2:
2414
+ continue
2415
+ h = [host(pid, x) for x in MODES]
2416
+ if None in h:
2417
+ unknown += 1
2418
+ elif h[0] == h[1]:
2419
+ same += 1
2420
+ else:
2421
+ diff += 1
2422
+ return same, diff, unknown
2423
+
2424
+
2425
+ def _modes_provenance(table: list[Row], meta: dict, host) -> list[str]:
2426
+ modes = {x: m for x, m in meta.items() if isinstance(x, int)}
2427
+ amd = [r for r in table if r.toolchain not in NVIDIA_TOOLCHAINS]
2428
+ progs = {r.program_id for r in amd}
2429
+ copy = {r.program_id for r in amd if r.xnack == COPY}
2430
+ if copy == progs:
2431
+ which = "every program (a full two-mode matrix)"
2432
+ else:
2433
+ which = ("the copy-mode subset only: the copy_mode_only programs and "
2434
+ "the memory-sensitive ones of "
2435
+ "catalog/omp/memory_modes.yaml (offload.engine.run_matrix."
2436
+ "copy_mode_subset)")
2437
+ same, diff, unknown = _node_counts(table, host)
2438
+ if not amd:
2439
+ return [] # An NVIDIA-only table (OpenACC C)
2440
+
2441
+ def run(x):
2442
+ m = modes.get(x, {})
2443
+ return (f"{m.get('host', '?')} (THP {m.get('thp_seen', '?')}, "
2444
+ f"started {m.get('started', '?')})")
2445
+ return [
2446
+ "- **Headline mode: HSA_XNACK=1 with transparent huge pages** (LC's "
2447
+ "example job script) for every program. **Copy mode "
2448
+ f"(HSA_XNACK=0)** for {len(copy)} of {len(progs)} programs: "
2449
+ f"{which}. A copy-mode column reads '{NOT_RUN}' for the others.",
2450
+ f"- HSA_XNACK=1 run on {run(HEADLINE)}; HSA_XNACK=0 run on "
2451
+ f"{run(COPY)}. Of the program x toolchain pairs with both modes, "
2452
+ f"{same} have both rows from one node, {diff} from different nodes "
2453
+ f"and {unknown} from unrecorded nodes; where the modes of the latter "
2454
+ "differ, the cells carry a dagger (†).",
2455
+ ]
2456
+
2457
+
2458
+ def _g90_provenance(table: list[Row], meta: dict) -> list[str]:
2459
+ """Where the gfx90a rows ran."""
2460
+ g = [r for r in table if arch_of(r.toolchain) == "gfx90a"]
2461
+ if not g:
2462
+ return []
2463
+ runs = (meta.get("g90") or {}).get("reruns", [])
2464
+ # the machine's name, not the node's (<machine>-gpu<n>.<domain> is <machine>)
2465
+ hosts = sorted({re.sub(r"-gpu\d+$", "", (rr.get("host") or "?").split(".")[0]) for rr in runs})
2466
+ return [
2467
+ f"- **gfx90a (Tioga, AMD Instinct MI250X, a discrete GPU): "
2468
+ f"HSA_XNACK=0 only** (XNACK is not "
2469
+ f"recommended on MI250X), {len({r.program_id for r in g})} "
2470
+ f"programs on " + ", ".join(TC_NAME[t] for t in G90) + ", nodes "
2471
+ + (", ".join(hosts) if hosts else "not recorded") + "; the same "
2472
+ "compiler releases with gfx90a as the offload arch "
2473
+ "(--offload-arch=gfx90a, craype-accel-amd-gfx90a for CCE, "
2474
+ "-march=gfx90a for GCC's amdgcn offload), the compilers' default "
2475
+ "('xnack any') code objects. Each compiler's third column."]
2476
+
2477
+
2478
+ def _gcc_flags_text(c_lang: bool) -> str:
2479
+ """GCC's flags on the AMD GPUs (its catalog template), when it has a column."""
2480
+ if not any(_reg().toolchains[t].family_id == "gcc" for t in TOOLCHAINS):
2481
+ return ""
2482
+ t = _reg().toolchains["gcc"]
2483
+ flags = t.family.flags["omp"]["c" if c_lang else "f90"].format(
2484
+ target="amdgcn-amdhsa", arch="<the GPU's arch>", latomic="")
2485
+ return f" GCC: {'gcc' if c_lang else 'gfortran'} {flags} -O2, no version flag."
2486
+
2487
+
2488
+ def _toolchains_text() -> str:
2489
+ """The status line's toolchains: the table's columns, by GPU."""
2490
+ amd = ", ".join(TC_NAME[t] for t in MI300A)
2491
+ other = ", ".join(TC_NAME[t] for t in TOOLCHAINS if t not in MI300A)
2492
+ nv = ", ".join(TC_NAME[t] for t in NV)
2493
+ return (f"Toolchains: {amd} on the MI300A (gfx942) and each one's other GPUs"
2494
+ + (f"; {other} on its other AMD GPUs only (not the MI300A)" if other else "")
2495
+ + (f"; {nv} on the A100 (sm_80)" if nv else "")
2496
+ + "; -O2, three runs per cell.")
2497
+
2498
+
2499
+ def _twins_provenance(table: list[Row], meta: dict) -> list[str]:
2500
+ """Where the rows of the twins on other machines than Tioga ran (MI300X, MI350X)."""
2501
+ from offload.engine.confirm.machines import load
2502
+ reg, out = _reg(), []
2503
+ for m in load().values():
2504
+ tws = [t for t in TWINS if reg.toolchains[t].machine == m.name and t not in G90]
2505
+ if not tws:
2506
+ continue
2507
+ runs = (meta.get("twins") or {}).get(m.name, [])
2508
+ hosts = sorted({re.sub(r"-gpu\d+$", "", (rr.get("host") or "?").split(".")[0]) for rr in runs})
2509
+ progs = {r.program_id for r in table if r.toolchain in tws}
2510
+ out.append(
2511
+ f"- **{m.arch} ({m.device}, a discrete GPU): copy memory (HSA_XNACK=0) only**, "
2512
+ f"{len(progs)} programs on " + ", ".join(TC_NAME[t] for t in tws) + ", nodes "
2513
+ + (", ".join(hosts) if hosts else "not recorded") + "; the same compiler releases "
2514
+ f"with {m.arch} as the offload arch. A column of its own after the gfx90a "
2515
+ "one; it does not enter the portable verdict.")
2516
+ return out
2517
+
2518
+
2519
+ def _nv_provenance(table: list[Row], meta: dict) -> list[str]:
2520
+ """Where the NVIDIA rows ran."""
2521
+ nv = [r for r in table if r.toolchain in NVIDIA_TOOLCHAINS]
2522
+ if not nv:
2523
+ return []
2524
+ runs = (meta.get("nvhpc") or {}).get("reruns", [])
2525
+ # the machine's name, not the node's (<machine>-gpu<n>.<domain> is <machine>)
2526
+ hosts = sorted({re.sub(r"-gpu\d+$", "", (rr.get("host") or "?").split(".")[0]) for rr in runs})
2527
+ comp = sorted({c for rr in runs for c in (rr.get("compilers") or {})
2528
+ .values() if c})
2529
+ return [
2530
+ f"- **NVIDIA A100 (wingtip, sm_80): "
2531
+ + ", ".join(TC_NAME[t] for t in NV) + "**, "
2532
+ f"{len({r.program_id for r in nv})} programs; NVHPC in separate memory "
2533
+ "(-gpu=mem:separate, the headline mode) and managed memory "
2534
+ "(-gpu=mem:managed), each mode its own build; -mp=gpu (OpenACC: "
2535
+ "-acc=gpu) -gpu=cc80 -O2, no OpenMP version option (every version "
2536
+ "slice compiles with the default)"
2537
+ + ("; GCC in separate memory only, -foffload=nvptx-none"
2538
+ if any(_reg().toolchains[t].family_id == "gcc" for t in NV) else "")
2539
+ + "; no unified memory on that node (not applicable). Host "
2540
+ + (", ".join(hosts) if hosts else "not recorded")
2541
+ + (f"; {'; '.join(comp)}" if comp else "") + "."]
2542
+
2543
+
2544
+ def render_markdown(table: list[Row], mismatches: list[Mismatch],
2545
+ claims: Path | list[Claim] | None = None,
2546
+ baseline: list[dict] | None = None,
2547
+ meta: dict | None = None,
2548
+ suite: dict | None = None) -> str:
2549
+ cl = (load_claims(claims) if isinstance(claims, (str, Path))
2550
+ else (claims or []))
2551
+ meta = meta or {}
2552
+ model = table_model(table)
2553
+ LINK = _paths(model)[3]
2554
+ acc = model == "acc"
2555
+ show_suite = not acc # omp_vv is an OpenMP suite
2556
+ # The headline tables are device offload only; host+device
2557
+ # pattern programs have their own section
2558
+ legal = [r for r in table if r.legal and r.scope == "device"]
2559
+ hostdev = [r for r in table if r.legal and r.scope != "device"]
2560
+ illegal = [r for r in table if not r.legal]
2561
+ modes = {x: m for x, m in meta.items() if isinstance(x, int)}
2562
+ host = program_hosts(meta)
2563
+ rocm = sorted({r for r in (meta.get("rocm") or [])})
2564
+ intro = [
2565
+ "# Support table: OpenMP target offload, free-form Fortran",
2566
+ "",
2567
+ "Status: **provisional**. Generated by compliance/report.py "
2568
+ "(tools/derive/support_table.py) from results/matrix.jsonl, the committed "
2569
+ f"oracle audit summary ([oracle-audit.md]({LINK['audit']})) and "
2570
+ f"[claims.yaml]({LINK['claims']}). Scope: OpenMP "
2571
+ f"{_versions_text(table)} atoms of "
2572
+ "the first slice, free-form Fortran only (OpenACC and C are outside "
2573
+ f"this slice). {_toolchains_text()}",
2574
+ ""]
2575
+ c_lang = table_lang(table) == "c"
2576
+ if c_lang:
2577
+ intro = [
2578
+ "# Support table: OpenMP target offload, C11",
2579
+ "",
2580
+ "Status: **provisional**. Generated by compliance/report.py "
2581
+ "(tools/derive/support_table.py --lang c) from results/matrix_c.jsonl, "
2582
+ "the committed C oracle audit summary "
2583
+ "([oracle-audit-c.md](../reports/oracle-audit-c.md)) and "
2584
+ f"[claims.yaml]({LINK['claims']}) (claims about C or C/C++ "
2585
+ "only; a claim that names a do compound covers its for twin). "
2586
+ f"Scope: OpenMP {_versions_text(table)} atoms of the first "
2587
+ "slice in C: the C ports of the Fortran programs "
2588
+ "(offload.engine.c_gen, offload.engine.f2c) and the C-only illegal "
2589
+ f"variants. {_toolchains_text()} The CSV has the "
2590
+ "Fortran table's columns plus `lang` (c here, f90 there).",
2591
+ ""]
2592
+ if acc and c_lang:
2593
+ intro = [
2594
+ "# Support table: OpenACC 3.4, C11 (NVIDIA HPC SDK)",
2595
+ "",
2596
+ "Status: **provisional**. Generated by compliance/report.py "
2597
+ "(tools/derive/support_table.py --model acc --lang c) from "
2598
+ "results/acc_matrix_c.jsonl and the NVIDIA oracle audit "
2599
+ "summaries (tests/data/acc_audit_summary_nvhpc*_c.json). Scope: "
2600
+ "the OpenACC 3.4 atoms of the slice in C: the C "
2601
+ "ports of the Fortran programs (offload.engine.acc_c_gen, "
2602
+ "offload.engine.f2c) without the Fortran-only .eqv./.neqv. "
2603
+ "reductions. Toolchains: NVHPC 25.11 and 26.9 nvc -acc=gpu, "
2604
+ "NVIDIA A100, separate and managed memory; CCE's C compiler and "
2605
+ "AFAR have no OpenACC (results/t37/accprobe) and are not run.",
2606
+ ""]
2607
+ elif acc:
2608
+ intro = [
2609
+ "# Support table: OpenACC 3.4, free-form Fortran (CCE)",
2610
+ "",
2611
+ "Status: **provisional**. Generated by compliance/report.py "
2612
+ "(tools/derive/support_table.py --model acc) from "
2613
+ "results/acc_matrix.jsonl and the committed oracle audit summary "
2614
+ f"([acc-oracle-audit.md]({LINK['audit']})). Scope: the OpenACC "
2615
+ "3.4 atoms of the slice (catalog/acc/), free-form Fortran "
2616
+ "only. Toolchains: Cray CCE "
2617
+ + ", ".join(TC_NAME[t] for t in TOOLCHAINS)
2618
+ + " (ftn -h acc -O2), MI300A (gfx942); AFAR (amdflang) does not "
2619
+ "support OpenACC and is not run.",
2620
+ ""]
2621
+ lines = intro + [
2622
+ "## Provenance and memory modes",
2623
+ "",
2624
+ f"- {len(table)} cells: {len(legal)} legal-program cells, "
2625
+ f"{len(hostdev)} host+device pattern cells and "
2626
+ f"{len(illegal)} illegal-variant cells (program x toolchain x "
2627
+ "HSA_XNACK).",
2628
+ "- Scope: device offload only. A construct "
2629
+ "that runs on the host and whose result never reaches a "
2630
+ + ("compute" if acc else "target") + " region is out of scope (the "
2631
+ "section 'Out of scope (host execution)' lists those atoms); host "
2632
+ "constructs that feed a "
2633
+ + ("compute" if acc else "target") + " region the way codes use "
2634
+ "them are host+device patterns, reported in their own section and "
2635
+ "never as an atom's headline result.",
2636
+ ] + _modes_provenance(table, meta, host) + _g90_provenance(
2637
+ table, meta) + _twins_provenance(table, meta) + _nv_provenance(table, meta) + [
2638
+ f"- **Rerun rows** (HSA_XNACK={x}): {len(rr.get('programs', []))} "
2639
+ f"programs rerun on {rr.get('host', '?')} (THP "
2640
+ f"{rr.get('thp_seen', '?')}, started {rr.get('started', '?')}) "
2641
+ f"after {_public(rr.get('why', 'a change'))}: "
2642
+ + (", ".join(rr.get("programs", [])) if len(rr.get("programs", []))
2643
+ <= 40 else "(the full list is in the run metadata)") + "."
2644
+ + (f" Note: {_public(rr['note'])}." if rr.get("note") else "")
2645
+ for x in MODES for rr in modes.get(x, {}).get("reruns", [])
2646
+ ] + [w for w in [_rerun_winners(modes)] if w] + [
2647
+ ("" if acc and c_lang else # NVHPC only (its own line)
2648
+ "- CCE compiles with -h acc -O2 (OpenMP off)." if acc else
2649
+ "- Every toolchain compiles a C program with -fopenmp-version= its "
2650
+ "version (both C front ends default to OpenMP 5.1); AFAR: amdclang "
2651
+ "-fopenmp --offload-arch=gfx942; CCE: craycc -fopenmp "
2652
+ "-fopenmp-targets=amdgcn-amd-amdhsa -Xopenmp-target=amdgcn-amd-amdhsa "
2653
+ "-march=gfx942 (the flags the craype-accel-amd-gfx942 module gives "
2654
+ "the cc wrapper; craycc -fopenmp alone builds a host-only "
2655
+ "executable). CCE 19 accepts -fopenmp-version=60 without a "
2656
+ "diagnostic but stays at OpenMP 5.1 (_OPENMP 202011)." if c_lang else
2657
+ "- AFAR compiles 6.0 programs with -fopenmp-version=60"
2658
+ + (", 5.0 programs with -fopenmp-version=50"
2659
+ if any(r.version == 50 for r in table) else "")
2660
+ + " and 5.2 "
2661
+ "programs with its default; CCE has no version flag (-fopenmp -O2).")
2662
+ + _gcc_flags_text(c_lang)
2663
+ + (f" ROCm loaded with CCE: {', '.join(rocm)}." if rocm else ""),
2664
+ ("- One run per trial: OpenACC has no counterpart of "
2665
+ "OMP_TARGET_OFFLOAD, so a failing run is not repeated; host "
2666
+ "execution shows as ran_on_host=1 (acc_on_device(acc_device_host) "
2667
+ "inside the compute region)." if acc else
2668
+ "- `OMP_TARGET_OFFLOAD=mandatory` on the primary run; a failing run "
2669
+ "is repeated without it to tell host fallback from a wrong answer."),
2670
+ "",
2671
+ "## How to read a cell",
2672
+ "",
2673
+ "- **correct on device**: the check passed and the region ran on the "
2674
+ "device; the evidence column says how strong that is: `construct` "
2675
+ "(the check depends on the construct), `probe` (a negative probe: "
2676
+ "the program names an absent item with the present modifier, so "
2677
+ "the run must end in the runtime's error termination; the cell "
2678
+ "reads 'error termination as required (negative probe)', and a run "
2679
+ "that went on reads 'no error termination (negative probe)'; or a "
2680
+ "compile-output probe: an error directive with at(compilation) whose "
2681
+ "message must appear as a warning, or whose fatal severity must stop "
2682
+ "the compile), "
2683
+ "`copy_mode_only` (the check "
2684
+ "separates the construct from a no-op only at HSA_XNACK=0; the "
2685
+ f"headline cell says so and carries {COPY_MARK}: the real check is "
2686
+ f"the copy-mode column, or the cell says '{COPY_NOT_RUN}'); the "
2687
+ "categories below have no separating oracle and are never credited "
2688
+ "as supported.",
2689
+ f"- **{NO_ORACLE_TEXT['hint_only']}** (`hint_only`): the spec lets "
2690
+ "an implementation ignore the form (a hint, an assertion the program "
2691
+ "makes, or a bound one-at-a-time execution meets); "
2692
+ f"**{NO_ORACLE_TEXT['implementation_defined']}** "
2693
+ "(`implementation_defined`): the spec leaves the behaviour to the "
2694
+ f"implementation; **{NO_ORACLE_TEXT['equivalent_default']}** "
2695
+ "(`equivalent_default`): by the spec the form is the behaviour "
2696
+ "without it. The cell reads so when the program compiled and ran "
2697
+ "cleanly; the spec sentence is in the citation column and in the "
2698
+ "section 'Atoms without a separating oracle'. Where the program "
2699
+ "checks what the form requires (e.g. the number of teams within "
2700
+ "the bounds) a failure reads as a wrong answer.",
2701
+ f"- **{LEGALITY_TEXT}** (`legality`): the atom's only effect is a "
2702
+ "compile-time restriction (default(none)); its oracle is the illegal "
2703
+ "variant in the illegal-variants table.",
2704
+ f"- **{UNVERIFIED_TEXT}** (`unverified`): the spec fixes the "
2705
+ "behaviour and a deterministic check is possible, but the program "
2706
+ "does not have it (the audit disposition says why).",
2707
+ "- **wrong answer (confirmed)**: the atom's oracle is audited valid "
2708
+ "(a mutant without the construct is killed on at least one "
2709
+ "toolchain). **wrong answer (oracle unaudited)** / **oracle "
2710
+ "unaudited**: measured outcome, but no toolchain passes the original "
2711
+ "program, so the oracle is unaudited; not a confirmed failure.",
2712
+ "- **crash: SIGABRT, HSA_XNACK=1 only**: the program aborts in that "
2713
+ "memory mode only (shown instead of a plain wrong answer).",
2714
+ "- **fails in copy mode: needs zero-copy memory**: the program needs "
2715
+ "HSA_XNACK=1; failing at HSA_XNACK=0 is expected and is not a "
2716
+ "feature result.",
2717
+ _stale_audit_note(legal),
2718
+ "- Illegal variants: **rejected** / **not rejected** only; acceptance "
2719
+ "of an illegal program is a neutral observation, never a wrong "
2720
+ "answer.",
2721
+ "",
2722
+ ]
2723
+ # summary counts
2724
+ n_copy = len({r.program_id for r in legal if r.xnack == COPY
2725
+ and r.toolchain not in NV})
2726
+ n_legal = len({r.program_id for r in legal})
2727
+ lines += ["## Summary (legal-program cells)", "",
2728
+ "Headline columns count every program; copy-mode columns count "
2729
+ f"only the programs run in copy mode ({n_copy} of {n_legal} "
2730
+ "legal programs).", ""]
2731
+ for v in sorted({r.version for r in legal}):
2732
+ rows = [r for r in legal if r.version == v]
2733
+ fams = Counter((family(r.label), r.toolchain, r.xnack) for r in rows)
2734
+ names = sorted({f for f, _, _ in fams},
2735
+ key=lambda f: (-sum(n for (g, _, _), n in fams.items()
2736
+ if g == f), f))
2737
+ n_atoms = len({r.atom for r in rows})
2738
+ lines += [f"### {_spec(v)} ({n_atoms} atoms)", "",
2739
+ f"| outcome | {_head_modes()} |",
2740
+ "|---|" + "---|" * len(columns())]
2741
+ for f in names:
2742
+ lines.append(f"| {f} | " + " | ".join(
2743
+ str(fams[(f, tc, x)]) for tc, x in columns())
2744
+ + " |")
2745
+ lines.append("")
2746
+ # illegal summary
2747
+ lines += ["### Illegal variants", "",
2748
+ f"| version | {_head_modes()} |",
2749
+ "|---|" + "---|" * len(columns())]
2750
+ for v in sorted({r.version for r in illegal}):
2751
+ rs = [r for r in illegal if r.version == v]
2752
+ lines.append(f"| {_vstr(v)} | " + " | ".join(
2753
+ f"{sum(1 for r in rs if r.toolchain == tc and r.xnack == x and r.outcome == 'rejected')}"
2754
+ f" rejected / "
2755
+ f"{sum(1 for r in rs if r.toolchain == tc and r.xnack == x and r.outcome == 'not_rejected')}"
2756
+ " not" for tc, x in columns()) + " |")
2757
+ lines.append("")
2758
+ lines += gate_section(table)
2759
+ # An NVIDIA-only table (OpenACC C) has no AMD mode sections
2760
+ amd_rows = any(r.toolchain not in NV for r in table)
2761
+ lines += _portable_section(legal) if amd_rows else []
2762
+ lines += _nv_portable_section(legal)
2763
+ lines += _mode_diff_section(table) if amd_rows else []
2764
+ lines += _nv_mode_diff_section(table)
2765
+ lines += _arch_diff_section(table)
2766
+ lines += _presence_x1_section(legal) if amd_rows else []
2767
+ lines += _claims_section(table, mismatches, cl) if cl else []
2768
+ # per-atom tables
2769
+ for v in sorted({r.version for r in legal}):
2770
+ by_atom: dict[str, list[Row]] = defaultdict(list)
2771
+ for r in legal:
2772
+ if r.version == v:
2773
+ by_atom[r.atom].append(r)
2774
+ suite_head = (" | ".join(f"omp_vv {TC_NAME[tc]}"
2775
+ for tc in MI300A) + " | "
2776
+ if show_suite else "")
2777
+ lines += [f"## {_spec(v)}: legal programs, one row per atom",
2778
+ "",
2779
+ COPY_MARK_TEXT + " " + DAGGER_TEXT]
2780
+ if show_suite:
2781
+ lines += ["omp_vv columns (second source, unaudited): narrow "
2782
+ "omp_vv tests that use the atom (at most one base "
2783
+ "clause), passes / fails by exit code, each test read "
2784
+ "at HSA_XNACK=1 or, when its result depends on copy "
2785
+ "semantics, at HSA_XNACK=0; blank when no narrow test "
2786
+ "uses the atom."]
2787
+ nv_head = "".join(f"{TC_NAME[t]} portable ("
2788
+ + " and ".join(nv_mode_name(t, x) for x in nv_modes(t)) + ") | "
2789
+ for t in NV)
2790
+ lines += ["",
2791
+ f"| atom | evidence | audit | {_head_modes()} | "
2792
+ f"{suite_head}portable ({PORTABLE_HEAD()}) | {nv_head}"
2793
+ "citation |",
2794
+ "|---|---|---|" + "---|" * len(columns())
2795
+ + ("---|" * len(MI300A) if show_suite else "")
2796
+ + "---|" * len(NV) + "---|---|"]
2797
+ for atom in sorted(by_atom):
2798
+ rows = by_atom[atom]
2799
+ if len({r.program_id for r in rows}) > 1:
2800
+ raise ValueError(f"several legal programs for {atom} at {v}")
2801
+ r0 = rows[0]
2802
+ cite, link = atom_citation(atom, v)
2803
+ basis = (f"; basis: {_md(r0.basis.split(': ', 1)[0])}"
2804
+ if r0.basis else "")
2805
+ suite_cells = (" | ".join(_suite_text(suite, atom, tc)
2806
+ for tc in MI300A) + " | "
2807
+ if show_suite else "")
2808
+ lines.append(
2809
+ f"| `{atom.removeprefix(model + '.')}` | {r0.evidence} | "
2810
+ f"{AUDIT_TEXT.get(r0.audit, r0.audit)} | "
2811
+ + " | ".join(_mode_cells(rows, host)) + " | "
2812
+ + suite_cells + _portable_cell(rows) + " | "
2813
+ + "".join(_nv_portable_cell(rows, t) + " | " for t in NV)
2814
+ + f"[{_md(cite)}]({link}){basis} |")
2815
+ lines.append("")
2816
+ lines += _no_oracle_section(legal)
2817
+ lines += _hostdev_section(hostdev, model, host)
2818
+ lines += _out_of_scope_section(table, model)
2819
+ # illegal detail
2820
+ lines += ["## Illegal variants: one row per rule", "",
2821
+ "Each program violates exactly one rule. Cells say only whether "
2822
+ "the compiler rejected it. " + DAGGER_TEXT,
2823
+ "",
2824
+ f"| version | rule | primary atom | {_head_modes()} | rule citation |",
2825
+ "|---|---|---|" + "---|" * len(columns()) + "---|"]
2826
+ by_prog: dict[str, list[Row]] = defaultdict(list)
2827
+ for r in illegal:
2828
+ by_prog[r.program_id].append(r)
2829
+ for pid in sorted(by_prog, key=lambda p: (by_prog[p][0].version,
2830
+ by_prog[p][0].violates or "")):
2831
+ rows = by_prog[pid]
2832
+ r0 = rows[0]
2833
+ cite, link = rule_citation(r0.violates)
2834
+ lines.append(f"| {_vstr(r0.version)} | {r0.violates} | "
2835
+ f"`{r0.atom.removeprefix(model + '.')}` | "
2836
+ + " | ".join(_mode_cells(rows, host))
2837
+ + f" | [{_md(cite)}]({link}) |")
2838
+ lines.append("")
2839
+ # failure details
2840
+ bad = [r for r in legal if family(r.label) not in (
2841
+ "correct on device", "correct on device (copy-mode evidence, HSA_XNACK=1)",
2842
+ NV_COPY_EVIDENCE_FAMILY,
2843
+ "accepted; conforms vacuously (no oracle)",
2844
+ "implementation-defined (no oracle)",
2845
+ "equivalent to the default (no oracle)",
2846
+ "accepted; oracle is the illegal variant", "accepted; unverified",
2847
+ "unobservable")]
2848
+ lines += ["## Failure details (legal programs)", "",
2849
+ "Every legal cell that is not a pass or an unseparable "
2850
+ "acceptance, with its gate status ('-' for a cell "
2851
+ "that is no failure) and the harness reason (clipped). "
2852
+ "Copy-mode "
2853
+ "(X=0) rows exist only for programs run in copy mode.", "",
2854
+ "| version | program | toolchain | X | report | gate | reason |",
2855
+ "|---|---|---|---|---|---|---|"]
2856
+ for r in bad:
2857
+ lines.append(f"| {_vstr(r.version)} | {r.program_id} | "
2858
+ f"{TC_NAME.get(r.toolchain, r.toolchain)} | {r.xnack} | "
2859
+ f"{_md(r.label)} | {r.gate or '-'} | "
2860
+ f"{_md(_clip(r.reason))} |")
2861
+ lines.append("")
2862
+ conf = [r for r in legal if r.confound]
2863
+ lines += ["## Confounded rejections", "",
2864
+ "**Not counted** (against the atom or in the claims): the "
2865
+ "compiler rejected another feature the program needs (a listed "
2866
+ "message), or its diagnostic is at an "
2867
+ + ("OpenACC" if acc else "OpenMP") + " directive line "
2868
+ "that does not carry the atom. **Counted with a caveat (‡)**: "
2869
+ "the rejection is the atom's own but needs context (a listed "
2870
+ "caveat), or the compiler points at a statement that uses a "
2871
+ "variable, not at the clause. Excluded cells are generator "
2872
+ "follow-ups.", ""]
2873
+ if conf:
2874
+ lines += ["| version | program | toolchain | X | counted | why |",
2875
+ "|---|---|---|---|---|---|"]
2876
+ lines += [f"| {_vstr(r.version)} | {r.program_id} | "
2877
+ f"{TC_NAME.get(r.toolchain, r.toolchain)} | {r.xnack} | "
2878
+ f"{'no' if r.excluded else 'yes ‡'} | "
2879
+ f"{_md(r.confound)} |" for r in conf]
2880
+ else:
2881
+ lines.append("None.")
2882
+ lines.append("")
2883
+ lines += _provisional_section(table)
2884
+ if not acc: # the omp_vv corpus baseline is OpenMP
2885
+ lines += _baseline_section(baseline or [], meta, c_lang)
2886
+ return "\n".join(lines) + "\n"
2887
+
2888
+
2889
+ def PORTABLE_HEAD() -> str:
2890
+ """What the portable column requires (the gfx90a cell too)."""
2891
+ return "X0, X1 and gfx90a" if G90 else "X0 and X1"
2892
+
2893
+
2894
+ def _portable_cell(rows: list[Row]) -> str:
2895
+ """The toolchains whose headline cell is portable (correct in both
2896
+ modes), then 'X1 only: ...' for those correct at HSA_XNACK=1 only."""
2897
+ head = [r for r in rows if r.xnack == HEADLINE]
2898
+ order = {t: i for i, t in enumerate(TOOLCHAINS)}
2899
+ head.sort(key=lambda r: order.get(r.toolchain, 9))
2900
+
2901
+ def names(v):
2902
+ return ", ".join(TC_NAME.get(r.toolchain, r.toolchain)
2903
+ + (f" ({r.mode_class})" if r.mode_class and
2904
+ v != "portable" else "")
2905
+ for r in head if r.portable == v)
2906
+ parts = [names("portable") or "none"]
2907
+ if names("x1_only"):
2908
+ parts.append(f"X1 only: {names('x1_only')}")
2909
+ if names("x0_only"):
2910
+ parts.append(f"X0 only: {names('x0_only')}")
2911
+ if names("x0_not_run"):
2912
+ parts.append(f"no X0 run: {names('x0_not_run')}")
2913
+ if names("gfx942_only"):
2914
+ parts.append(f"MI300A only: {names('gfx942_only')}")
2915
+ return "; ".join(parts)
2916
+
2917
+
2918
+ def _nv_portable_cell(rows: list[Row], tc: str) -> str:
2919
+ """An NVIDIA toolchain's portable verdict (its headline row),
2920
+ with the mode class when the modes differ."""
2921
+ head = [r for r in rows if r.toolchain == tc
2922
+ and r.xnack == nv_modes(tc)[0]]
2923
+ if not head:
2924
+ return NOT_RUN
2925
+ r = head[0]
2926
+ return r.portable + (f" ({r.mode_class})" if r.mode_class else "")
2927
+
2928
+
2929
+ def _nv_portable_section(legal: list[Row]) -> list[str]:
2930
+ """Per version, an NVIDIA toolchain's headline cells by
2931
+ portable verdict (correct in separate and in managed memory)."""
2932
+ if not NV:
2933
+ return []
2934
+ out = ["## Portable verdict, NVIDIA (separate and managed memory)", "",
2935
+ ", ".join(TC_NAME[t] for t in NV)
2936
+ + " on an NVIDIA A100 (wingtip, sm_80): the headline "
2937
+ "mode is separate memory (-gpu=mem:separate, the compiler's "
2938
+ "default: separate host and device copies, the discrete-GPU "
2939
+ "mode); managed memory (-gpu=mem:managed) puts dynamically "
2940
+ "allocated data in CUDA managed memory. Unified memory "
2941
+ "(-gpu=mem:unified) is not applicable on this machine (no HMM: "
2942
+ "the runtime refuses to start). Portable = correct in both "
2943
+ "modes. Per toolchain, the legal programs' verdicts: "
2944
+ + "; ".join(f"**{k}**: {v}" for k, v in NV_PORTABLE.items())
2945
+ + ".", ""]
2946
+ head = [r for r in legal if r.toolchain in NV
2947
+ and r.xnack == nv_modes(r.toolchain)[0]]
2948
+ for v in sorted({r.version for r in head}):
2949
+ rs = [r for r in head if r.version == v]
2950
+ c = Counter((r.portable, r.toolchain) for r in rs)
2951
+ out += [f"### {_spec(v)}", "",
2952
+ "| verdict | " + " | ".join(TC_NAME[t] for t in NV)
2953
+ + " |", "|---|" + "---|" * len(NV)]
2954
+ for k in NV_PORTABLE:
2955
+ if any(c[(k, t)] for t in NV):
2956
+ out.append(f"| {k} | " + " | ".join(
2957
+ str(c[(k, t)]) for t in NV) + " |")
2958
+ out.append("")
2959
+ return out
2960
+
2961
+
2962
+ def _nv_mode_diff_section(table: list[Row]) -> list[str]:
2963
+ """Every legal NVIDIA headline cell whose separate and
2964
+ managed cells differ, with the class of the difference."""
2965
+ if not NV:
2966
+ return []
2967
+ rows = [r for r in table if r.legal and r.mode_class
2968
+ and r.toolchain in NV]
2969
+ cells = {(r.program_id, r.toolchain, r.xnack): r for r in table}
2970
+ out = ["## Memory-mode differences, NVIDIA (separate versus managed)",
2971
+ "",
2972
+ "Legal programs whose NVHPC outcome differs between separate and "
2973
+ "managed memory, with the class of the difference "
2974
+ "(catalog/mode_differences.yaml nvhpc_entries): "
2975
+ + "; ".join(f"**{k}**: {v}" for k, v in NV_MODE_CLASSES.items())
2976
+ + "; **unclassified**: no entry yet.", ""]
2977
+ if not rows:
2978
+ return out + ["None.", ""]
2979
+ c = Counter((r.mode_class, r.toolchain) for r in rows)
2980
+ out += ["| class | " + " | ".join(TC_NAME[t] for t in NV) + " |",
2981
+ "|---|" + "---|" * len(NV)]
2982
+ for k in list(NV_MODE_CLASSES) + ["unclassified"]:
2983
+ if any(c[(k, t)] for t in NV):
2984
+ out.append(f"| {k} | " + " | ".join(str(c[(k, t)]) for t in NV)
2985
+ + " |")
2986
+ out += ["", "| version | program | toolchain | separate | managed | "
2987
+ "class | finding / why |", "|---|---|---|---|---|---|---|"]
2988
+ for r in rows:
2989
+ e = nv_mode_entry(r.program_id, r.toolchain) or {}
2990
+ m = cells.get((r.program_id, r.toolchain, nv_modes(r.toolchain)[1]))
2991
+ why = (f"data/findings/{e['finding']}; " if e.get("finding")
2992
+ else "") + " ".join(str(e.get("why", "")).split())
2993
+ out.append(f"| {_vstr(r.version)} | {r.program_id} | "
2994
+ f"{TC_NAME.get(r.toolchain, r.toolchain)} | "
2995
+ f"{_md(r.label)} | {_md(m.label if m else '')} | "
2996
+ f"{r.mode_class} | {_md(_clip(why, 300))} |")
2997
+ return out + [""]
2998
+
2999
+
3000
+ def _portable_section(legal: list[Row]) -> list[str]:
3001
+ """Per version, the headline cells by portable verdict and
3002
+ toolchain."""
3003
+ out = ["## Portable verdict (correct at HSA_XNACK=0 and 1"
3004
+ + (" and on gfx90a)" if G90 else ")"), "",
3005
+ "The result a user can rely on for portable code (a discrete GPU "
3006
+ "has separate host and device memory) is **correct in both "
3007
+ "memory modes**: at HSA_XNACK=1 (unified memory) a missing or "
3008
+ "wrong map often still gives the right answer, so a mapping "
3009
+ "error shows only at HSA_XNACK=0. "
3010
+ + ("**and on the discrete GPU**, Tioga's MI250X "
3011
+ "(gfx90a, HSA_XNACK=0, separate HBM): portable = correct on "
3012
+ "MI300A at HSA_XNACK=1, at HSA_XNACK=0 and on gfx90a. "
3013
+ if G90 else "")
3014
+ + "Per toolchain, the legal "
3015
+ "programs' verdicts: "
3016
+ + "; ".join(f"**{k}**: {v}" for k, v in PORTABLE.items())
3017
+ + ".", ""]
3018
+ head = [r for r in legal if r.xnack == HEADLINE]
3019
+ for v in sorted({r.version for r in head}):
3020
+ rs = [r for r in head if r.version == v]
3021
+ c = Counter((r.portable, r.toolchain) for r in rs)
3022
+ out += [f"### {_spec(v)}", "",
3023
+ "| verdict | " + " | ".join(TC_NAME[t] for t in TOOLCHAINS)
3024
+ + " |", "|---|" + "---|" * len(TOOLCHAINS)]
3025
+ for k in PORTABLE:
3026
+ if any(c[(k, t)] for t in TOOLCHAINS):
3027
+ out.append(f"| {k} | " + " | ".join(
3028
+ str(c[(k, t)]) for t in TOOLCHAINS) + " |")
3029
+ out.append("")
3030
+ return out
3031
+
3032
+
3033
+ def _mode_diff_section(table: list[Row]) -> list[str]:
3034
+ """Every legal headline cell whose two modes differ, with the
3035
+ class of the difference (catalog/mode_differences.yaml)."""
3036
+ rows = [r for r in table if r.legal and r.mode_class
3037
+ and r.toolchain not in NV]
3038
+ copy = {(r.program_id, r.toolchain): r for r in table
3039
+ if r.xnack == COPY}
3040
+ c = Counter((r.mode_class, r.toolchain) for r in rows)
3041
+ out = ["## Memory-mode differences", "",
3042
+ "Legal programs (device and host+device) whose outcome differs "
3043
+ "between HSA_XNACK=1 and HSA_XNACK=0 on a toolchain, with the "
3044
+ "class of the difference (catalog/mode_differences.yaml): "
3045
+ + "; ".join(f"**{k}**: {v}" for k, v in MODE_CLASSES.items())
3046
+ + "; **unclassified**: no entry yet.", ""]
3047
+ if not rows:
3048
+ return out + ["None.", ""]
3049
+ out += ["| class | " + " | ".join(TC_NAME[t] for t in TOOLCHAINS)
3050
+ + " |", "|---|" + "---|" * len(TOOLCHAINS)]
3051
+ for k in list(MODE_CLASSES) + ["unclassified"]:
3052
+ if any(c[(k, t)] for t in TOOLCHAINS):
3053
+ out.append(f"| {k} | " + " | ".join(str(c[(k, t)])
3054
+ for t in TOOLCHAINS) + " |")
3055
+ out += ["", "| version | program | toolchain | X=1 | X=0 | class | "
3056
+ "finding / why |", "|---|---|---|---|---|---|---|"]
3057
+ for r in rows:
3058
+ e = mode_entry(r.program_id, r.toolchain) or {}
3059
+ x0 = copy.get((r.program_id, r.toolchain))
3060
+ why = (f"data/findings/{e['finding']}; " if e.get("finding")
3061
+ else "") + " ".join(str(e.get("why", "")).split())
3062
+ out.append(f"| {_vstr(r.version)} | {r.program_id} | "
3063
+ f"{TC_NAME.get(r.toolchain, r.toolchain)} | "
3064
+ f"{_md(r.label)} | {_md(x0.label if x0 else '')} | "
3065
+ f"{r.mode_class} | {_md(_clip(why, 300))} |")
3066
+ return out + [""]
3067
+
3068
+
3069
+ def _arch_diff_section(table: list[Row]) -> list[str]:
3070
+ """Every legal headline cell whose MI300A copy-mode cell and
3071
+ gfx90a cell differ, with the class of the difference."""
3072
+ if not G90:
3073
+ return []
3074
+ rows = [r for r in table if r.legal and r.arch_class]
3075
+ cells = {(r.program_id, r.toolchain, r.xnack): r for r in table}
3076
+ pairs = [r for r in table if r.legal and r.xnack == HEADLINE
3077
+ and (r.program_id, g90_of(r.toolchain), COPY) in cells
3078
+ and (r.program_id, r.toolchain, COPY) in cells]
3079
+ out = ["## gfx90a (MI250X) versus gfx942 (MI300A) copy mode", "",
3080
+ "Both run with separate host and device memory (HSA_XNACK=0); "
3081
+ "the MI250X is a discrete GPU (its own HBM), the MI300A an APU "
3082
+ "whose copy mode emulates one. Legal programs (device and "
3083
+ "host+device) whose gfx90a outcome differs from the MI300A "
3084
+ f"copy-mode outcome: {len(rows)} of {len(pairs)} compared "
3085
+ "cells, with the class of the difference "
3086
+ "(catalog/mode_differences.yaml, arch_entries): "
3087
+ + "; ".join(f"**{k}**: {v}" for k, v in ARCH_CLASSES.items())
3088
+ + "; **unclassified**: no entry yet.", ""]
3089
+ if not rows:
3090
+ return out + ["None.", ""]
3091
+ c = Counter((r.arch_class, r.toolchain) for r in rows)
3092
+ out += ["| class | " + " | ".join(TC_NAME[t] for t in TOOLCHAINS)
3093
+ + " |", "|---|" + "---|" * len(TOOLCHAINS)]
3094
+ for k in list(ARCH_CLASSES) + ["unclassified"]:
3095
+ if any(c[(k, t)] for t in TOOLCHAINS):
3096
+ out.append(f"| {k} | " + " | ".join(str(c[(k, t)])
3097
+ for t in TOOLCHAINS) + " |")
3098
+ out += ["", "| version | program | toolchain | MI300A X=1 | MI300A X=0 "
3099
+ "| gfx90a | class | finding / why |",
3100
+ "|---|---|---|---|---|---|---|---|"]
3101
+ for r in rows:
3102
+ e = arch_entry(r.program_id, r.toolchain) or {}
3103
+ x0 = cells.get((r.program_id, r.toolchain, COPY))
3104
+ g = cells.get((r.program_id, g90_of(r.toolchain), COPY))
3105
+ why = (f"data/findings/{e['finding']}; " if e.get("finding")
3106
+ else "") + " ".join(str(e.get("why", "")).split())
3107
+ out.append(f"| {_vstr(r.version)} | {r.program_id} | "
3108
+ f"{TC_NAME.get(r.toolchain, r.toolchain)} | "
3109
+ f"{_md(r.label)} | {_md(x0.label if x0 else '')} | "
3110
+ f"{_md(g.label if g else '')} | {r.arch_class} | "
3111
+ f"{_md(_clip(why, 300))} |")
3112
+ return out + [""]
3113
+
3114
+
3115
+ def _presence_x1_section(legal: list[Row]) -> list[str]:
3116
+ """Fix: the copy-mode programs whose presence checks were run
3117
+ at HSA_XNACK=1 too, and the cells where they fail there."""
3118
+ rs = [r for r in legal if r.xnack == HEADLINE and r.presence_x1]
3119
+ out = ["## Presence checks at HSA_XNACK=1", "",
3120
+ "A copy_mode_only program skips its checks under unified memory, "
3121
+ "so its HSA_XNACK=1 cell is no evidence. Presence "
3122
+ "(omp_target_is_present) is defined in both modes, so "
3123
+ "tools/campaigns/presence_x1.py ran each program with only its presence "
3124
+ "checks unguarded at HSA_XNACK=1 (tests/data/"
3125
+ "presence_x1_summary.json). A failure there makes the cell not "
3126
+ "portable (the portable verdict counts it as an HSA_XNACK=1 "
3127
+ "failure).", ""]
3128
+ if not rs:
3129
+ return out + ["None.", ""]
3130
+ c = Counter((r.toolchain, r.presence_x1) for r in rs)
3131
+ outs = sorted({r.presence_x1 for r in rs})
3132
+ out += ["| outcome | " + " | ".join(TC_NAME[t] for t in TOOLCHAINS)
3133
+ + " |", "|---|" + "---|" * len(TOOLCHAINS)]
3134
+ out += [f"| {o} | " + " | ".join(str(c[(t, o)]) for t in TOOLCHAINS)
3135
+ + " |" for o in outs]
3136
+ bad = [r for r in rs if r.presence_x1 == "wrong_answer"
3137
+ and r.evidence == "copy_mode_only"]
3138
+ out += ["", "Cells whose HSA_XNACK=1 'correct (copy-mode evidence)' "
3139
+ "hides a failing presence check: "
3140
+ + (", ".join(f"{r.program_id} ({TC_NAME.get(r.toolchain, r.toolchain)})"
3141
+ for r in bad) or "none") + ".", ""]
3142
+ return out
3143
+
3144
+
3145
+ def _hostdev_section(rows: list[Row], model: str, host=None) -> list[str]:
3146
+ """The host+device pattern programs, one row per program,
3147
+ apart from the headline per-atom tables."""
3148
+ out = ["## Host+device patterns (not headline results)", "",
3149
+ "Host constructs that feed a "
3150
+ + ("compute" if model == "acc" else "target") + " region the way "
3151
+ "codes use them (an exception to the device-offload scope), one program per "
3152
+ "pattern (id ending in -hostdev). Each is judged on its device "
3153
+ "result (the check and ran_on_host of the device region); it is "
3154
+ "not the atom's oracle (no mutation audit) and never counts as "
3155
+ "the atom's support. The atom's headline is its device-only "
3156
+ "program in the tables above.", ""]
3157
+ if not rows:
3158
+ return out + ["None.", ""]
3159
+ out += [f"| version | atom | program | realistic use | {_head_modes()} |",
3160
+ "|---|---|---|---|" + "---|" * len(columns())]
3161
+ by: dict[str, list[Row]] = defaultdict(list)
3162
+ for r in rows:
3163
+ by[r.program_id].append(r)
3164
+ for pid in sorted(by, key=lambda p: (by[p][0].version, p)):
3165
+ rs = by[pid]
3166
+ r0 = rs[0]
3167
+ out.append(f"| {_vstr(r0.version)} | "
3168
+ f"`{r0.atom.removeprefix(model + '.')}` | {pid} | "
3169
+ f"{_md(r0.use)} | " + " | ".join(_mode_cells(rs, host))
3170
+ + " |")
3171
+ return out + [""]
3172
+
3173
+
3174
+ def _out_of_scope_section(table: list[Row], model: str) -> list[str]:
3175
+ """The atoms removed from the slice as host tests
3176
+ (catalog/<model>/out_of_scope.yaml), with the reason and the spec
3177
+ sentence."""
3178
+ from offload.engine.catalog import out_of_scope_atoms
3179
+ lang = table_lang(table) if table else "f90"
3180
+ out = ["## Out of scope (host execution)", "",
3181
+ "Atoms whose construct of interest runs only on the host, or "
3182
+ "whose effect never reaches a "
3183
+ + ("compute" if model == "acc" else "target") + " region: not "
3184
+ "device offload, so they have no program, no cell and no "
3185
+ f"verdict here (catalog/{model}/out_of_scope.yaml; each sentence "
3186
+ "is checked against the spec text).", "",
3187
+ "| version | atom | reason | spec |", "|---|---|---|---|"]
3188
+ n = 0
3189
+ for v in sorted({r.version for r in table}):
3190
+ for atom, e in sorted(out_of_scope_atoms(v, lang).items()):
3191
+ sp = e["spec"].get(v) or {}
3192
+ out.append(f"| {_vstr(v)} | `{atom.removeprefix(model + '.')}` "
3193
+ f"| {_md(' '.join(e['reason'].split()))} | "
3194
+ f"{_md(sp.get('cite', ''))}: \"{_md(sp.get('quote', ''))}\" |")
3195
+ n += 1
3196
+ if not n:
3197
+ out.append("| - | none | - | - |")
3198
+ return out + [""]
3199
+
3200
+
3201
+ def _no_oracle_section(legal: list[Row]) -> list[str]:
3202
+ """One row per (version, atom) whose program has no separating oracle
3203
+ , with the spec sentence that makes it so."""
3204
+ seen: dict[tuple, Row] = {}
3205
+ for r in legal:
3206
+ if r.evidence in NO_ORACLE_TEXT:
3207
+ seen.setdefault((r.version, r.atom), r)
3208
+ model = table_model(legal)
3209
+ out = ["## Atoms without a separating oracle", "",
3210
+ "No single run can tell these forms from their absence, and the "
3211
+ f"spec says why (catalog/{model}/no_oracle.yaml; every sentence is "
3212
+ "checked against the spec text). Their cells read as the "
3213
+ "category, never as support; a rejection or a wrong answer keeps "
3214
+ "its outcome.", "",
3215
+ "| version | atom | category | spec basis |", "|---|---|---|---|"]
3216
+ for (v, atom), r in sorted(seen.items()):
3217
+ out.append(f"| {_vstr(v)} | `{atom.removeprefix(model + '.')}` | "
3218
+ f"{NO_ORACLE_TEXT[r.evidence]} | {_md(r.basis)} |")
3219
+ if not seen:
3220
+ out.append("| - | none | - | - |")
3221
+ return out + [""]
3222
+
3223
+
3224
+ def read_baseline(path: Path) -> list[dict]:
3225
+ p = Path(path)
3226
+ if not p.is_file():
3227
+ return []
3228
+ return [json.loads(line) for line in p.read_text().splitlines()
3229
+ if line.strip()]
3230
+
3231
+
3232
+ def _is_g90_run(rr: dict) -> bool:
3233
+ """A recorded run of gfx90a toolchains only."""
3234
+ tcs = rr.get("toolchains") or []
3235
+ return bool(tcs) and all(arch_of(t) == "gfx90a" for t in tcs)
3236
+
3237
+
3238
+ def _twin_machine(rr: dict) -> str | None:
3239
+ """The machine of a recorded run of twins of one machine other than Tioga
3240
+ (MI300X, MI350X) only, else None."""
3241
+ reg = _reg()
3242
+ tcs = [reg.toolchains.get(t) for t in rr.get("toolchains") or []]
3243
+ ms = {t.machine for t in tcs if t and t.twin_of and reg.twin_tag(t.name) != "gfx90a"}
3244
+ return ms.pop() if tcs and len(ms) == 1 and all(
3245
+ t and t.twin_of and t.machine in ms for t in tcs) else None
3246
+
3247
+
3248
+ def _is_nv_run(rr: dict) -> bool:
3249
+ """A recorded run of NVIDIA toolchains only: kept apart
3250
+ from the AMD modes' runs."""
3251
+ tcs = rr.get("toolchains") or []
3252
+ return bool(tcs) and all(t in NVIDIA_TOOLCHAINS for t in tcs)
3253
+
3254
+
3255
+ def read_meta(results: Path) -> dict:
3256
+ """Run metadata per memory mode from <results>.xnack{0,1}.jsonl.meta.json
3257
+ and the ROCm versions recorded with CCE results."""
3258
+ meta: dict = {}
3259
+ results = Path(results)
3260
+ for x in MODES:
3261
+ p = results.with_name(f"{results.stem}.xnack{x}.jsonl.meta.json")
3262
+ if p.is_file():
3263
+ meta[x] = json.loads(p.read_text())
3264
+ stem = "baseline_c" if results.stem.endswith("_c") else "baseline"
3265
+ b = results.with_name(f"{stem}.xnack{x}.jsonl.meta.json")
3266
+ if b.is_file():
3267
+ meta[f"baseline{x}"] = json.loads(b.read_text())
3268
+ # reruns written straight into the merged matrix (run_matrix.py rerun
3269
+ # --out results/matrix.jsonl) are recorded in its own meta file; each
3270
+ # joins its mode's reruns once, in start order
3271
+ merged = results.with_name(f"{results.name}.meta.json")
3272
+ if merged.is_file():
3273
+ for rr in json.loads(merged.read_text()).get("reruns", []):
3274
+ x = rr.get("xnack")
3275
+ if _is_nv_run(rr): # Wingtip rows, not an AMD mode's
3276
+ meta.setdefault("nvhpc", {}).setdefault("reruns", []).append(
3277
+ rr)
3278
+ continue
3279
+ if _twin_machine(rr): # MI300X, MI350X rows, not a mode's
3280
+ meta.setdefault("twins", {}).setdefault(_twin_machine(rr), []).append(rr)
3281
+ continue
3282
+ if x not in MODES:
3283
+ continue
3284
+ if _is_g90_run(rr): # Tioga rows, not a mode's
3285
+ meta.setdefault("g90", {}).setdefault("reruns", []).append(
3286
+ rr)
3287
+ continue
3288
+ m = meta.setdefault(x, {})
3289
+ have = m.setdefault("reruns", [])
3290
+ if any(h.get("started") == rr.get("started") and
3291
+ h.get("host") == rr.get("host") for h in have):
3292
+ continue
3293
+ have.append(rr)
3294
+ have.sort(key=lambda r: r.get("started") or "")
3295
+ # Baseline rows replaced in the merged baseline file
3296
+ # (run_matrix.py replace --baseline) are recorded in its meta file
3297
+ stem = "baseline_c" if results.stem.endswith("_c") else "baseline"
3298
+ bmerged = results.with_name(f"{stem}.jsonl.meta.json")
3299
+ if bmerged.is_file():
3300
+ for rr in json.loads(bmerged.read_text()).get("reruns", []):
3301
+ x = rr.get("xnack")
3302
+ if _is_nv_run(rr):
3303
+ meta.setdefault("baseline_nvhpc", {}).setdefault(
3304
+ "reruns", []).append(rr)
3305
+ continue
3306
+ if x not in MODES:
3307
+ continue
3308
+ if _is_g90_run(rr):
3309
+ meta.setdefault("baseline_g90", {}).setdefault(
3310
+ "reruns", []).append(rr)
3311
+ continue
3312
+ have = meta.setdefault(f"baseline{x}", {}).setdefault(
3313
+ "reruns", [])
3314
+ if not any(h.get("started") == rr.get("started") and
3315
+ h.get("host") == rr.get("host") for h in have):
3316
+ have.append(rr)
3317
+ if results.is_file():
3318
+ meta["rocm"] = sorted({json.loads(line).get("rocm") or ""
3319
+ for line in results.read_text().splitlines()
3320
+ if line.strip()} - {""})
3321
+ return meta
3322
+
3323
+
3324
+ def write_report(results: Path = MATRIX, claims: Path = CLAIMS,
3325
+ audit: Path = AUDIT, baseline: Path = BASELINE,
3326
+ out_md: Path | None = None, out_csv: Path | None = None
3327
+ ) -> tuple[list[Row], list[Mismatch]]:
3328
+ """Build the table of `results` and write its Markdown and CSV (C
3329
+ results, results/matrix_c.jsonl with C_AUDIT and
3330
+ C_BASELINE)."""
3331
+ results = Path(results)
3332
+ # A changed canary of the campaign stops the report
3333
+ from offload.engine import canary
3334
+ canary.require_unchanged(
3335
+ canary.files_for(results), str(results.name),
3336
+ campaign_rows=[json.loads(x) for x in results.read_text()
3337
+ .splitlines() if x.strip()]
3338
+ if results.is_file() else None)
3339
+ out_md = out_md or results.with_name("support_table.md")
3340
+ out_csv = out_csv or results.with_name("support_table.csv")
3341
+ table = build_table(results, audit=audit)
3342
+ bad = sorted({(r.program_id, r.toolchain) for r in table
3343
+ if r.mode_class == "unclassified"})
3344
+ if bad:
3345
+ raise ValueError(f"unclassified memory-mode differences (add them "
3346
+ f"to {MODE_DIFF_FILE.name}; NVIDIA: nvhpc_entries)"
3347
+ f": {bad}")
3348
+ bad = sorted({f"{r.program_id} {r.toolchain}: {why}" for r in table
3349
+ if r.mode_class and r.mode_class != "unclassified" and
3350
+ (why := (nv_mode_class_problem if r.toolchain in NV else
3351
+ mode_class_problem)(r.mode_class, r.label,
3352
+ r.copy_label))})
3353
+ if bad:
3354
+ raise ValueError(f"memory-mode classes the cells contradict "
3355
+ f"(fix {MODE_DIFF_FILE.name}): {bad}")
3356
+ bad = sorted({(r.program_id, r.toolchain) for r in table
3357
+ if r.arch_class == "unclassified"})
3358
+ if bad:
3359
+ raise ValueError(f"unclassified gfx90a versus gfx942 differences "
3360
+ f"(add them to {MODE_DIFF_FILE.name} "
3361
+ f"arch_entries): {bad}")
3362
+ mism = compare_claims(table, claims)
3363
+ base = read_baseline(baseline)
3364
+ meta = read_meta(results)
3365
+ from offload.engine.ompvv import suite_verdicts
3366
+ suite = suite_verdicts(base, {r.atom for r in table})
3367
+ md = render_markdown(table, mism, claims=claims, baseline=base,
3368
+ meta=meta, suite=suite)
3369
+ comp = companion_section(results)
3370
+ if comp:
3371
+ md = md.rstrip("\n") + "\n\n" + "\n".join(comp)
3372
+ Path(out_md).write_text(md)
3373
+ Path(out_csv).write_text(render_csv(table, suite=suite, meta=meta))
3374
+ return table, mism