-
Notifications
You must be signed in to change notification settings - Fork 555
Add name argument to @wp.kernel (GH-1561) #1570
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5131,6 +5131,14 @@ def make_full_qualified_name(func: str | Callable) -> str: | |
| return re.sub("[^0-9a-zA-Z_]+", "", func.replace(".", "__")) | ||
|
|
||
|
|
||
| # matches a valid C identifier: a letter or underscore followed by letters, digits, or underscores | ||
| _C_IDENTIFIER_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") | ||
|
|
||
|
|
||
| def is_valid_c_identifier(name: str) -> bool: | ||
| return _C_IDENTIFIER_RE.match(name) is not None | ||
|
Comment on lines
+5135
to
+5139
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: No, reserved C/C++ keywords (such as for, while, class, and template) are not legal identifiers for function names [1][2][3]. In C and C++, keywords are reserved by the language specification to provide specific syntactic and semantic meaning to the compiler [1][4]. Because these terms are essential to the language's core structures, they cannot be redefined, overloaded, or used as names for identifiers such as functions, variables, classes, or templates [1][2][5]. The only exception to this rule is the use of these tokens within an attribute-token context (introduced in C++11), where they are not treated as reserved keywords [1][4]. Attempting to use a keyword as a function name will result in a compilation error [3]. Furthermore, beyond explicit keywords, certain other identifiers are reserved for use by the implementation (such as those containing a double underscore or beginning with an underscore followed by an uppercase letter), and using these as function names can lead to undefined behavior [1][2][6]. Citations:
🏁 Script executed: # Check the current code at the specified location
head -5140 warp/_src/codegen.py | tail -15Repository: NVIDIA/warp Length of output: 572 🏁 Script executed: # Search for any existing reserved keyword handling in codegen.py
rg -n "keyword|reserved|RESERVED" warp/_src/codegen.py | head -20Repository: NVIDIA/warp Length of output: 883 🏁 Script executed: # Check if this validator is used for kernel names or function validation
rg -n "is_valid_c_identifier\|_C_IDENTIFIER_RE" warp/_src/codegen.pyRepository: NVIDIA/warp Length of output: 37 🏁 Script executed: # The is_valid_c_identifier might not be in use yet. Let's check if it's called anywhere
rg "is_valid_c_identifier" warp/_src/Repository: NVIDIA/warp Length of output: 209 🏁 Script executed: # Check if there's any validation of kernel names in the kernel decorator or related code
rg -n "def kernel\|`@kernel`" warp/_src/codegen.py | head -10Repository: NVIDIA/warp Length of output: 37 🏁 Script executed: # Search for where kernel names are processed or validated
rg -n "name=" warp/_src/codegen.py | grep -i "kernel\|func" | head -15Repository: NVIDIA/warp Length of output: 952 🏁 Script executed: # Check where is_valid_c_identifier is called in context.py and what it validates
rg -B 5 -A 5 "is_valid_c_identifier" warp/_src/context.pyRepository: NVIDIA/warp Length of output: 492 🏁 Script executed: # Also check the kernel decorator implementation to understand the kernel name validation flow
fd "kernel.py\|__init__.py" warp/_src/ --type f | head -5Repository: NVIDIA/warp Length of output: 37 🏁 Script executed: # Check if there's any existing keyword filtering or handling anywhere in warp codebase
rg -i "keyword|reserved" warp/_src/ --type py | grep -v "\.pyc" | head -20Repository: NVIDIA/warp Length of output: 2137 🏁 Script executed: # Look at the make_full_qualified_name function to understand how kernel names are generated
rg -B 3 -A 8 "make_full_qualified_name" warp/_src/codegen.pyRepository: NVIDIA/warp Length of output: 473 🏁 Script executed: # Check C/C++ codegen targets to understand which keywords Warp needs to avoid
rg -n "cuda\|c_\+\+\|CPP\|GLSL" warp/_src/codegen.py | grep -i "target\|backend\|language" | head -10Repository: NVIDIA/warp Length of output: 37 🏁 Script executed: # Check if Warp targets CUDA or other languages beyond C/C++
rg -n "class.*CodeGen\|def.*codegen" warp/_src/codegen.py | head -10Repository: NVIDIA/warp Length of output: 37 🏁 Script executed: # Verify if CUDA has additional reserved keywords beyond C++
web_search
CUDA C/C++ reserved keywords additional to C++ standardRepository: NVIDIA/warp Length of output: 149 🏁 Script executed: # Check if there are existing tests for identifier validation
fd "test.*codegen\|test.*kernel" warp/tests/ --type fRepository: NVIDIA/warp Length of output: 37 🏁 Script executed: # Find test files related to codegen or kernel
find warp/tests -name "*test*codegen*" -o -name "*test*kernel*" | head -10Repository: NVIDIA/warp Length of output: 294 🏁 Script executed: # Check if there are existing tests that might validate identifier handling
rg "is_valid_c_identifier\|_C_IDENTIFIER_RE" warp/tests/Repository: NVIDIA/warp Length of output: 37 🌐 Web query:
💡 Result: In CUDA C/C++ (as implemented by nvcc), the main “reserved keywords beyond the C standard library” are the CUDA language extension keywords/specifiers—i.e., identifiers starting with double underscores that nvcc recognizes and gives special meaning. Key CUDA-reserved specifiers (language extensions) documented by NVIDIA include: - host / device / global (execution space specifiers for where code runs, or to declare kernels) [1] - restrict (pointer restrict qualifier) [2][1] - launch_bounds (function qualifier for launch bounds on global functions) [2][1] - grid_constant (qualifier for parameters) [2][1] - noinline, forceinline, inline_hint (inlining control qualifiers) [2][1] - cluster_dims (cluster dimensions qualifier) [2][1] - device, constant, shared, managed (memory-space/storage specifiers for variables) [1] Additionally, NVIDIA’s CUDA programming guide documents that CUDA source files use these extensions and that any source file containing CUDA extensions must be compiled with nvcc. [3] Citations:
🏁 Script executed: # Check test_codegen.py for any identifier validation tests
rg -n "is_valid_c_identifier\|C_IDENTIFIER" warp/tests/test_codegen.pyRepository: NVIDIA/warp Length of output: 37 🏁 Script executed: # Look at test_codegen.py to understand what's already tested
head -100 warp/tests/test_codegen.pyRepository: NVIDIA/warp Length of output: 1991 🏁 Script executed: # Check if there are any actual kernel name validations in tests
rg -B 3 -A 3 "name.*=.*`@wp`\.kernel\|`@wp`\.kernel.*name" warp/tests/ | head -30Repository: NVIDIA/warp Length of output: 37 🏁 Script executed: # Let's check the context where the validator is used to understand the full picture
rg -B 10 -A 5 "is_valid_c_identifier" warp/_src/context.pyRepository: NVIDIA/warp Length of output: 582 🏁 Script executed: # Verify there are no existing keyword checks elsewhere in the kernel path
rg -n "keyword\|reserved.*keyword" warp/_src/context.pyRepository: NVIDIA/warp Length of output: 37 🏁 Script executed: # Check what happens when a keyword is used as kernel name currently
rg -n "ValueError.*valid.*C.*identifier" warp/_src/Repository: NVIDIA/warp Length of output: 192 Reject reserved C/C++ keywords in the identifier validator Line 5138 currently accepts keyword tokens like Proposed fix _C_IDENTIFIER_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
+_C_RESERVED_KEYWORDS = {
+ # include the full C/C++ keyword set used by Warp codegen targets
+ "auto", "break", "case", "char", "const", "continue", "default", "do",
+ "double", "else", "enum", "extern", "float", "for", "goto", "if", "int",
+ "long", "register", "return", "short", "signed", "sizeof", "static",
+ "struct", "switch", "typedef", "union", "unsigned", "void", "volatile",
+ "while", "class", "namespace", "template", "typename",
+}
def is_valid_c_identifier(name: str) -> bool:
- return _C_IDENTIFIER_RE.match(name) is not None
+ return _C_IDENTIFIER_RE.fullmatch(name) is not None and name not in _C_RESERVED_KEYWORDS🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| def codegen_struct(struct, device="cpu", indent_size=4): | ||
| name = struct.native_name | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -805,8 +805,13 @@ def __init__(self, func, key=None, module=None, options=None, code_transformers= | |
| else: | ||
| self.module = module | ||
|
|
||
| # Keep track of the qualified name of the Python function used to create | ||
| # this kernel, so it can still be identified even when the kernel is given | ||
| # a custom name (key). | ||
| self.qualname = warp._src.codegen.make_full_qualified_name(func) | ||
|
|
||
| if key is None: | ||
| self.key = warp._src.codegen.make_full_qualified_name(func) | ||
| self.key = self.qualname | ||
| else: | ||
| self.key = key | ||
|
|
||
|
|
@@ -1371,6 +1376,7 @@ def kernel2( | |
| def kernel( | ||
| f: Callable | None = None, | ||
| *, | ||
| name: str | None = None, | ||
| enable_backward: bool | None = None, | ||
| launch_bounds: tuple[int, ...] | int | None = None, | ||
| module: Module | Literal["unique"] | str | None = None, | ||
|
|
@@ -1419,6 +1425,9 @@ def my_kernel_fast(a: wp.array[float], b: wp.array[float]): | |
|
|
||
| Args: | ||
| f: The function to be registered as a kernel. | ||
| name: Overrides the kernel's key. If ``None``, the function's | ||
| qualified name will be used as the key. The name must be a valid | ||
| C identifier; raises a ``ValueError`` otherwise. | ||
| enable_backward: If False, the backward pass will not be | ||
| generated. | ||
| launch_bounds: CUDA ``__launch_bounds__`` attribute for the | ||
|
|
@@ -1448,6 +1457,13 @@ def my_kernel_fast(a: wp.array[float], b: wp.array[float]): | |
| def wrapper(f, *args, **kwargs): | ||
| kernel_options = {} | ||
|
|
||
| if name is None: | ||
| key = warp._src.codegen.make_full_qualified_name(f) | ||
| else: | ||
| if not warp._src.codegen.is_valid_c_identifier(name): | ||
| raise ValueError(f"@wp.kernel for '{f.__name__}': name '{name}' is not a valid C identifier.") | ||
| key = name | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please fix decorator-form This branch registers a kernel only under the explicit from typing import Any
import warp as wp
@wp.kernel(name="custom_scale")
def scale(x: wp.array[Any], s: Any):
i = wp.tid()
x[i] = s * x[i]
@wp.overload
def scale(x: wp.array[wp.float32], s: wp.float32):
...Observed on this branch: Direct overload registration still works: wp.overload(scale, [wp.array[wp.float32], wp.float32])so the regression is specifically in the decorator-form lookup. I think the fix should preserve lookup by the original Python qualified name, or otherwise teach Please also add a regression test covering
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, thanks for spotting this issue. From looking at the code, there are a couple ways to address this. I took the liberty of implementing one of them. For completeness, these are the options considered:
Open to iterating over this. |
||
|
|
||
| if enable_backward is not None: | ||
| kernel_options["enable_backward"] = enable_backward | ||
|
|
||
|
|
@@ -1493,7 +1509,7 @@ def wrapper(f, *args, **kwargs): | |
| # Create the kernel object and register it with the module | ||
| k = Kernel( | ||
| func=f, | ||
| key=warp._src.codegen.make_full_qualified_name(f), | ||
| key=key, | ||
| module=m, | ||
| options=kernel_options, | ||
| ) | ||
|
|
@@ -2997,7 +3013,27 @@ def _detach_references(self): | |
| # find kernel corresponding to a Python function | ||
| def _find_kernel(self, func): | ||
| qualname = warp._src.codegen.make_full_qualified_name(func) | ||
| return self.kernels.get(qualname) | ||
|
|
||
| # A kernel's key matches the qualified name unless the user specifies a | ||
| # custom name for the kernel. | ||
| # First, check the fast path: kernels registered under their ``qualname``. | ||
| kernel = self.kernels.get(qualname) | ||
| if kernel is not None: | ||
| return kernel | ||
|
|
||
| # Otherwise, the kernel was renamed. Fall back to matching via the | ||
| # ``qualname`` attribute. | ||
| # In this case, several kernels may share the same qualified name (e.g. | ||
| # kernels generated in a closure). We take the most recently registered | ||
| # kernel as the intended target. This assumes, that the ``@wp.overload`` | ||
| # is defined just after the overloaded kernel, ``self.kernels`` preserves | ||
| # insertion order and that the ``self.kernels`` keys aren't overwritten. | ||
| match = None | ||
| for kernel in self.kernels.values(): | ||
| if kernel.qualname == qualname: | ||
| match = kernel | ||
|
|
||
| return match | ||
|
|
||
| # collect all referenced functions / structs | ||
| # given the AST of a function or kernel | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is_valid_c_identifieronly checks the structural regex pattern — it accepts any syntactically valid identifier, including C reserved words (int,float,void,if,for,while,return,struct, etc.) and CUDA built-ins (threadIdx,blockDim, etc.). A user who calls@wp.kernel(name="int")will pass this check but still get a cryptic compiler error, which is the exact failure mode this check was added to prevent.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!