5454
5555
5656class Kernel :
57- def __init__ (self , name ):
57+ def __init__ (self , name , requires ):
5858 self .name = name
59- self .requires = [] # list of (pattern, expected_count)
60- self .forbids = []
59+ self .requires = requires # list of (pattern, expected_count)
6160
6261
6362def parse_kernels (source : Path ):
6463 kernels = []
65- pending_requires , pending_forbids = [], []
64+ pending_requires = []
6665 arch = None
6766
6867 for line in source .read_text ().splitlines ():
6968 if m := CHECK_COUNT_RE .search (line ):
7069 pending_requires .append ((m .group (2 ).strip (), int (m .group (1 ))))
7170 elif m := CHECK_RE .search (line ):
72- # `None` means "at least once" (exact count is not checked)
7371 pending_requires .append ((m .group (1 ).strip (), 1 ))
7472 elif m := CHECK_NOT_RE .search (line ):
75- pending_forbids .append (m .group (1 ).strip ())
73+ pending_requires .append (( m .group (1 ).strip (), 0 ))
7674 elif m := ARCH_RE .search (line ):
7775 arch = m .group (1 )
7876 elif m := KERNEL_RE .search (line ):
79- kernel = Kernel (m .group (1 ))
80- kernel .requires = pending_requires
81- kernel .forbids = pending_forbids
82- kernels .append (kernel )
83- pending_requires , pending_forbids = [], []
77+ kernels .append (Kernel (m .group (1 ), pending_requires ))
78+ pending_requires = []
8479
8580 return kernels , arch
8681
8782
88- def get_ptx (nvcc , source , arch ):
83+ def get_ptx (nvcc , source , arch , verbose = False ):
8984 kf_root = Path (__file__ ).parent .parent .resolve () / "include"
9085 cmd = [
9186 nvcc ,
@@ -99,6 +94,9 @@ def get_ptx(nvcc, source, arch):
9994 str (source ),
10095 ]
10196
97+ if verbose :
98+ print (f"$ { ' ' .join (cmd )} " , file = sys .stderr )
99+
102100 result = subprocess .run (cmd , capture_output = True , text = True )
103101 if result .returncode != 0 :
104102 print (f"command failed: { ' ' .join (cmd )} " , file = sys .stderr )
@@ -128,25 +126,49 @@ def extract_ptx_block(ptx: str, kernel_name: str) -> str:
128126 return None
129127
130128
131- def check_file (source : Path , nvcc : str , arch : str ) -> "tuple[int, int]" :
132- """Returns (num_kernels, num_failed) for this source file."""
133- kernels , arch_directive = parse_kernels (source )
129+ def check_file (
130+ source : Path ,
131+ nvcc : str ,
132+ arch : str ,
133+ verbose : bool = False ,
134+ kernel_filter : "set[str] | None" = None ,
135+ ) -> "tuple[int, int, int]" :
136+ """Returns (num_passed, num_failed, num_skipped) for this source file."""
137+ all_kernels , arch_directive = parse_kernels (source )
134138 arch = arch_directive or arch
135139
136- kernels = [k for k in kernels if k .requires or k . forbids ]
137- if not kernels :
140+ checkable = [k for k in all_kernels if k .requires ]
141+ if not checkable :
138142 print (
139143 f"error: { source } has no kernel preceded by "
140144 "// CHECK:, // CHECK-COUNT-<N>:, or // CHECK-NOT: directives" ,
141145 file = sys .stderr ,
142146 )
143- return 0 , 1
147+ return 0 , 1 , 0
148+
149+ # Kernels with no directives at all are skipped silently; they were never meant to be
150+ # checked. Kernels excluded by --kernel are also skipped, but reported below.
151+ num_skipped = len (all_kernels ) - len (checkable )
152+
153+ if kernel_filter is not None :
154+ kernels = [k for k in checkable if k .name in kernel_filter ]
155+ num_skipped += len (checkable ) - len (kernels )
156+ for k in checkable :
157+ if k .name not in kernel_filter :
158+ print (f"SKIP: { k .name } (excluded by --kernel)" )
159+ else :
160+ kernels = checkable
161+
162+ if not kernels :
163+ # Nothing in this file matches --kernel; not an error, just nothing to do here.
164+ return 0 , 0 , num_skipped
144165
145- ptx = get_ptx (nvcc , source , arch )
166+ ptx = get_ptx (nvcc , source , arch , verbose = verbose )
146167 if ptx is None :
147168 print (f"FAIL: { source } : nvcc failed to compile this file" , file = sys .stderr )
148- return len ( kernels ) , len (kernels )
169+ return 0 , len (kernels ), num_skipped
149170
171+ num_passed = 0
150172 num_failed = 0
151173
152174 for kernel in kernels :
@@ -162,18 +184,24 @@ def check_file(source: Path, nvcc: str, arch: str) -> "tuple[int, int]":
162184
163185 failures = []
164186 for pattern , expected_count in kernel .requires :
165- if expected_count is None :
166- if not re .search (pattern , block , re .MULTILINE ):
167- failures .append (f"CHECK pattern not found: { pattern !r} " )
168- else :
169- actual_count = len (re .findall (pattern , block , re .MULTILINE ))
170- if actual_count != expected_count :
171- failures .append (
172- f"CHECK pattern found { actual_count } time(s), expected { expected_count } : { pattern !r} "
173- )
174- for pattern in kernel .forbids :
175- if re .search (pattern , block , re .MULTILINE ):
176- failures .append (f"CHECK-NOT pattern found: { pattern !r} " )
187+ actual_count = len (re .findall (pattern , block , re .MULTILINE ))
188+ ok = (
189+ actual_count >= 1
190+ if expected_count is None
191+ else actual_count == expected_count
192+ )
193+ want = "at least 1" if expected_count is None else str (expected_count )
194+
195+ if verbose :
196+ status = "success" if ok else "FAILURE"
197+ print (
198+ f"[{ kernel .name } ] { status } (want { want } , found { actual_count } ): { pattern !r} " ,
199+ file = sys .stderr ,
200+ )
201+ if not ok :
202+ failures .append (
203+ f"pattern found { actual_count } time(s), expected { want } : { pattern !r} "
204+ )
177205
178206 if failures :
179207 num_failed += 1
@@ -184,9 +212,16 @@ def check_file(source: Path, nvcc: str, arch: str) -> "tuple[int, int]":
184212 )
185213 print (block , file = sys .stderr )
186214 else :
215+ num_passed += 1
187216 print (f"OK: { kernel .name } (ptx, { arch } )" )
217+ if verbose :
218+ print (
219+ f"----- generated ptx for { kernel .name } ({ arch } ) -----" ,
220+ file = sys .stderr ,
221+ )
222+ print (block , file = sys .stderr )
188223
189- return len ( kernels ) , num_failed
224+ return num_passed , num_failed , num_skipped
190225
191226
192227def main ():
@@ -195,22 +230,52 @@ def main():
195230 )
196231 parser .add_argument ("source" , type = Path , nargs = "+" )
197232 parser .add_argument ("--nvcc" , default = "nvcc" )
198- parser .add_argument ("--verbose" , action = "store_true" )
233+ parser .add_argument (
234+ "--verbose" ,
235+ action = "store_true" ,
236+ help = "print the nvcc command, per-pattern match results, and the generated PTX "
237+ "for every kernel, not just the ones that fail" ,
238+ )
199239 parser .add_argument (
200240 "--arch" , help = "used unless overridden by a // ARCH: directive" , default = "sm_80"
201241 )
242+ parser .add_argument (
243+ "--kernel" ,
244+ action = "append" ,
245+ help = "only check the kernel with this name, skipping all others (repeatable)" ,
246+ )
202247 args = parser .parse_args ()
203248
204- total_kernels = 0
249+ kernel_filter = set (args .kernel ) if args .kernel else None
250+
251+ total_passed = 0
205252 total_failed = 0
253+ total_skipped = 0
206254
207255 for source in args .source :
208256 print (f"=== { source } ===" )
209- num_kernels , num_failed = check_file (source , args .nvcc , args .arch )
210- total_kernels += num_kernels
257+ num_passed , num_failed , num_skipped = check_file (
258+ source ,
259+ args .nvcc ,
260+ args .arch ,
261+ verbose = args .verbose ,
262+ kernel_filter = kernel_filter ,
263+ )
264+ total_passed += num_passed
211265 total_failed += num_failed
266+ total_skipped += num_skipped
212267
213- print (f"TEST PASSED: { total_kernels - total_failed } / { total_kernels } " )
268+ if kernel_filter is not None and total_passed + total_failed == 0 :
269+ print (
270+ f"error: no kernel named { sorted (kernel_filter )} found in any source file" ,
271+ file = sys .stderr ,
272+ )
273+ sys .exit (1 )
274+
275+ total = total_passed + total_failed + total_skipped
276+ print (
277+ f"RESULT: { total_passed } passed, { total_failed } failed, { total_skipped } skipped ({ total } total)"
278+ )
214279
215280 if total_failed :
216281 sys .exit (1 )
0 commit comments