Skip to content

[Feature] Add full Windows support and fix related cross-platform issues - #2093

Merged
SiriusNEO merged 15 commits into
tile-ai:mainfrom
sepcnt:windows
May 6, 2026
Merged

SiriusNEO merged 15 commits into
tile-ai:mainfrom
sepcnt:windows

Conversation

@sepcnt

@sepcnt sepcnt commented Apr 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR adds end-to-end Windows support for TileLang across build, packaging, runtime loading, and developer setup.
Along the way, it also fixes several cross-platform issues uncovered during the Windows bring-up, especially around CUDA toolkit discovery, MSVC compatibility, runtime DLL loading, logging, and layout/reducer handling.

Main changes

  • add native Windows support in the build and packaging flow
  • support pip-installed CUDA toolkits on Windows, including NVCC/NVRTC-based workflows
  • add Windows-specific MSVC environment bootstrap helpers
  • add a Windows helper script for setting up a local development environment
  • improve runtime DLL discovery and PATH setup on Windows
  • update installation docs and packaging metadata for Windows

Bug fixes

  • avoid enabling CUDA by default when no CUDA toolkit is available
  • fix NVRTC import-library handling on Windows
  • fix several MSVC build issues, including:
    • Python debug import library auto-linking
    • TVM_DLL import/export behavior
    • designated initializer compilation
    • large CUDA intrinsic dispatch paths in codegen
  • normalize Windows file paths for TVM_LOG_DEBUG
  • make reducer/layout handling more robust when reducer annotations are absent or layout information is still in hint form
  • improve runtime library lookup behavior for local/dev builds on Windows

TODO / Follow-ups

  • clean up and optimize the dynamic-loading (dynload) polyfill
  • investigate the ROCm regression; currently suspected to be a compatibility break introduced by a recent nightly
  • optimize and reuse CUDA toolkit lookup results to avoid redundant probing
  • fix concurrency/thread-safety issues in the autotuner on Windows

Known issues

  • MSVC is significantly slower than GCC/Clang at compiling CUTE headers; we may need to introduce on-demand / lazy includes to keep build times reasonable on Windows.
  • cutlass-dsl does not officially support Windows, so any code paths depending on it are currently unavailable in the Windows build.
  • CUDA 13 requires 128-byte alignment in some places, but MSVC only supports up to 64-byte alignment for __declspec(align(...)) if is not newer than 17.10. As a workaround, we use a torch-style fallback that degrades to the largest alignment MSVC accepts; this matches PyTorch's behavior but means the strict CUDA 13 alignment guarantee is not preserved on MSVC.

Summary by CodeRabbit

Release Notes

  • New Features
    • Full Windows platform support with MSVC compiler integration and native CUDA runtime compatibility
    • Windows-specific NVRTC and CUDA toolkit installation via package manager
  • Improvements
    • Enhanced Windows build infrastructure and CMake configuration
    • Improved Windows installation documentation with Visual Studio Developer Command Prompt guidance
    • Cross-platform dynamic library loading support
    • Better debug logging configuration capabilities
  • Bug Fixes
    • Fixed CUDA stub compatibility on Windows
    • Corrected platform-specific library path handling

@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the TileLang project.

Please remember to run pre-commit run --all-files in the root directory of the project to ensure your changes are properly linted and formatted. This will help ensure your contribution passes the format check.

We appreciate you taking this step! Our team will review your contribution, and we look forward to your awesome work! 🚀

@coderabbitai

coderabbitai Bot commented Apr 24, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request adds comprehensive Windows support to TileLang's build system and runtime environment. Changes include CMake helpers for Python interpreter selection and Windows import library generation, a cross-platform dynamic library loading abstraction replacing POSIX-only dlopen calls, CUDA toolkit discovery enhancements supporting pip-installed CUDA, MSVC compiler integration for Windows builds, and Python-level runtime DLL path management. The changes span build configuration, CUDA stub infrastructure, packaging, and runtime initialization to enable TileLang to build and run on Windows with CUDA support.

Changes

Cohort / File(s) Summary
CMake Build System
CMakeLists.txt, cmake/FindPipCUDAToolkit.cmake
Adds tilelang_get_python_interpreter() and tilelang_generate_windows_import_library() CMake functions; reworks CUDA discovery to support pip-installed CUDA with multi-candidate Python fallback; updates Windows CUDA toolchain selection, stub linking strategy (using /WHOLEARCHIVE for TileLang objects), and Cython build flags (Py_NO_LINK_LIB=1).
CUDA Toolkit/Pip Discovery
cmake/find_pip_cuda.py
Expands CUDA root discovery from single-source lookup to multi-candidate search across pip packages and system paths; adds explicit CLI input normalization; factors NVCC detection into separate _find_nvcc() helper; platforms-specific build-prep hardened to Linux-only; success JSON now includes resolved NVCC path and computed library_dir field.
Windows Import Library Generation
cmake/generate_windows_import_lib.py
New Python CLI tool that parses DLL exports via dumpbin, generates .def file with deduplicated symbol list, and writes ASCII DEF format; used by CMake to create Windows import libraries from DLLs.
Windows Z3 Integration
cmake/pypi-z3/FindZ3.cmake
Changes Windows Z3 discovery from find_library to DLL-based search; generates Windows import library when missing using the new tilelang_generate_windows_import_library() CMake function; non-Windows behavior unchanged.
Cross-Platform Dynamic Library Loading
src/target/stubs/dynlib.h
New header-only abstraction for dynamic library loading across Windows/POSIX with functions: dynlib_open(), dynlib_sym(), dynlib_error(), dynlib_find_loaded(); Windows uses LoadLibrary/GetProcAddress/EnumProcessModules, POSIX uses dlopen/dlsym/dlerror.
CUDA Driver Stub Windows Support
src/target/stubs/cuda.cc, src/target/stubs/cuda.h
Migrates from POSIX dlopen/dlsym to dynlib abstraction; removes Windows build restriction; switches candidate library from libcuda.so* to nvcuda.dll on Windows; adds CUDAAPI markers to exported wrapper function return types; header guards exported function declarations to omit on Windows (keep on Cygwin).
CUDA Runtime Stub Windows Support
src/target/stubs/cudart.cc
Migrates to dynlib abstraction; removes Windows-only build restriction; Windows now attempts candidate cudart64_*.dll paths; non-Windows checks for already-loaded symbol first via dynlib_find_loaded before opening candidates; TILELANG_CUDART_STUB_API macro emptied on Windows/Cygwin.
NVRTC Stub Windows Support
src/target/stubs/nvrtc.cc
Reworks lazy-loading to use dynlib abstraction with platform-specific candidate filenames; suppresses API visibility on Windows/Cygwin; adds check for already-loaded nvrtcVersion via dynlib_find_loaded.
Code Generation Infrastructure
src/target/codegen_cuda.cc, src/target/codegen_cuda.h
Refactors late intrinsic call handling into dedicated HandleLateIntrinsicCall() helper method; routes CUDA fast-math, IEEE rounding, packed math, RNG, warp reductions, and atomic operations through the helper; explicit return true statements prevent unintended fallback.
Layout & Operator Handling
src/layout/gemm_layouts.cc, src/op/operator.h, src/op/parallel.cc, src/transform/layout_reducer.cc
Adds cross-compiler TILELANG_COMPILER_UNREACHABLE macro (MSVC __assume(0) vs GCC/Clang __builtin_unreachable()); adds <array> include; defensive null-checks before dereferencing optional annotations in layout and parallel visitors.
Runtime Logging Enhancements
src/runtime/logging.cc
Adds filename normalization (FileToVLogMapKey), TvmLogDebugSettings::ParseSpec for parsing TVM_LOG_DEBUG environment variable, VerboseEnabledImpl for per-file verbosity checking, and VLogContext::str() for serializing context stacks.
Build Configuration & Packaging
pyproject.toml, .gitattributes, .gitignore
Updates pyproject.toml to add Windows CUDA toolkit components, ninja, torch support, and pytorch-cu130 index; adds .gitattributes rule for *.cmd CRLF handling; adds uv.lock to .gitignore.
Installation Documentation
docs/get_started/Installation.md
Expands "Building from Source" prerequisites from Linux-only to Linux or Windows; adds Windows setup instructions for Python 3, CMake, MSVC Build Tools, and running commands from Visual Studio Developer Command Prompt.
Python Windows Library Management
tilelang/__init__.py, tilelang/env.py, tilelang/libinfo.py
Windows-specific initialization: sets os.RTLD_LAZY default, prepends runtime library directories to PATH using os.add_dll_directory() (Python ≥3.8), and registers CUDA subdirectories when CUDA_HOME is set; env.py ensures TL_LIBS directories are in PATH; libinfo.py adds get_dll_directories() and multi-candidate library search with Windows-specific candidates.
Python MSVC Compiler Integration
tilelang/contrib/msvc.py, tilelang/contrib/cc.py, tilelang/contrib/nvcc.py
New msvc.py module provides Windows-specific build helpers: discovers/caches MSVC environment via VsDevCmd.bat, normalizes compiler options, translates GCC attributes to MSVC, extracts/deduplicates exports, and invokes cl.exe with /LD; cc.py updated to locate MSVC cl.exe on Windows; nvcc.py adds Windows-specific /Zc:preprocessor flag and MSVC environment error diagnostics.
Kernel Cache & JIT Adapters
tilelang/cache/kernel_cache.py, tilelang/jit/adapter/libgen.py, tilelang/jit/adapter/tvm_ffi.py, tilelang/jit/adapter/wrapper.py
Windows JIT compilation: kernel_cache.py returns MSVC-based fcompile hook on Windows and hashes additional DLLs for cache keys; libgen.py uses .dll extension on Windows and platform-specific compiler flags; tvm_ffi.py sets MSVC fcompile on Windows; wrapper.py adds TL_EXPORT macro for Windows __declspec(dllexport) on wrapper functions.
CUDA Driver & Library Loading
tilelang/carver/arch/driver/cuda_driver.py
Adds _load_cudart() helper that probes Windows cudart64_13/12/110/11.dll candidates and falls back to libcudart.so on non-Windows.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

enhancement, dependencies, windows, cuda, build-system

Suggested reviewers

  • LeiWang1999
  • XuehaiPan
  • SiriusNEO

Poem

🐰 A rabbit hops through Windows wide,
With CUDA stubs now by its side,
Dynamic libs and import magic,
No more build errors—what a gain!
CMake helpers, DLL paths aligned—
Windows support, at last, we find! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main feature: full Windows support and cross-platform fixes. It directly reflects the primary objective of the changeset across build, packaging, runtime, and documentation updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tilelang/jit/adapter/wrapper.py (1)

60-86: ⚠️ Potential issue | 🟡 Minor

CPU backend call() is not exported on Windows — inconsistent with CUDA/HIP paths.

PREDEF_HOST_FUNC (used by CUDA and HIP via inheritance) now wraps call with TL_EXPORT, but TLCPUSourceWrapper.CALL_PREFIX (lines 751–758) still emits plain extern "C" int32_t call(...). When the CPU backend lib is built on Windows via tilelang.contrib.msvc.create_shared, the MSVC /EXPORT auto-discovery only matches __tvm_ffi_* symbols (_TVM_FFI_EXPORT_RE in msvc.py), so call will be missing from the DLL exports and the ctypes adapter lookup (self.lib.call) will fail with an "unresolved symbol" error.

🐛 Proposed fix for CPU wrapper
     CALL_PREFIX = textwrap.dedent("""
         `#ifdef` __cplusplus
-        extern "C"
+        extern "C" TL_EXPORT
         `#endif`
         int32_t call({}) {{
           return {};
         }}
     """)

Since CALL_PREFIX is appended after INIT_FUNC = PREDEF_INIT_FUNC.format(""), the TL_EXPORT macro is already defined at the point call is emitted.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/jit/adapter/wrapper.py` around lines 60 - 86, The CPU backend's
exported call symbol is not marked with TL_EXPORT, causing missing export on
Windows; change the CPU wrapper emission so the call signature uses the same
export macro as CUDA/HIP by replacing the plain 'extern "C" int32_t call(...)'
emitted by TLCPUSourceWrapper.CALL_PREFIX with the TL_EXPORT-enabled prefix (as
defined by PREDEF_HOST_FUNC / PREDEF_INIT_FUNC where TL_EXPORT is already
defined), ensuring the CPU shared lib exposes call and the ctypes adapter can
resolve self.lib.call.
🧹 Nitpick comments (20)
src/runtime/logging.cc (2)

108-124: Drop the unreachable return settings; after LOG(FATAL).

LogFatalImpl is declared [[noreturn]] (line 62), so LOG(FATAL) never returns. The three return settings; statements on lines 110, 115, and 123 are dead code and may also trigger "unreachable code" warnings under MSVC /W4 (which this PR now exercises). The very first LOG(FATAL) at lines 100-102 already omits the trailing return for consistency — apply the same treatment here.

♻️ Proposed fix
     if (!std::getline(spec_stream, level, ',')) {
       LOG(FATAL) << "TVM_LOG_DEBUG ill-formed at position " << tell_pos(level)
                  << ": expecting \"=<level>\" after \"" << name << "\"";
-      return settings;
     }
     if (level.empty()) {
       LOG(FATAL) << "TVM_LOG_DEBUG ill-formed at position " << tell_pos(level)
                  << ": empty level after \"" << name << "\"";
-      return settings;
     }

     char *end_of_level = nullptr;
     int level_val = static_cast<int>(strtol(level.c_str(), &end_of_level, 10));
     if (end_of_level != level.c_str() + level.size()) {
       LOG(FATAL) << "TVM_LOG_DEBUG ill-formed at position " << tell_pos(level)
                  << ": invalid level: \"" << level << "\"";
-      return settings;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/logging.cc` around lines 108 - 124, Remove the unreachable
"return settings;" statements that follow the LOG(FATAL) calls in the
level-parsing block (the checks for ill-formed token, empty level, and invalid
numeric level) because LOG(FATAL) routes to LogFatalImpl which is [[noreturn]];
keep behavior consistent with the earlier LOG(FATAL) that already omits a
trailing return and leave only the LOG(FATAL) calls in place.

3-4: Include <sstream> explicitly.

ParseSpec uses std::istringstream (line 85) and VLogContext::str() uses std::stringstream (line 150), but <sstream> is not directly included. The file currently relies on transitive includes through <tvm/runtime/logging.h> or implementation details, which is not guaranteed across different standard library implementations or versions. Add the explicit include to ensure this translation unit is self-sufficient.

♻️ Proposed fix
`#include` <algorithm>
`#include` <cstdlib>
`#include` <ctime>
`#include` <iomanip>
`#include` <iostream>
+#include <sstream>
`#include` <string>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/logging.cc` around lines 3 - 4, The translation unit uses
std::istringstream and std::stringstream in ParseSpec and VLogContext::str() but
doesn't include <sstream>, relying on transitive includes; add an explicit
`#include` <sstream> at the top of the file so ParseSpec and VLogContext::str()
have the required header and the compilation is self-contained.
src/transform/layout_reducer.cc (2)

174-175: Nit: stale doxygen reference.

The docstring still mentions analyzer_->const_int_bound, but the implementation now routes through GetThreadConstIntBound(...). Worth updating so the contract matches the code.

📝 Proposed doc tweak
- * - Reads and relies on `thread_var_`, `analyzer_->const_int_bound`, and
- *   `var_to_buffer_`.
+ * - Reads and relies on `thread_var_`, `GetThreadConstIntBound(thread_var_,
+ *   *analyzer_)`, and `var_to_buffer_`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/transform/layout_reducer.cc` around lines 174 - 175, Update the function
docstring to remove the stale reference to analyzer_->const_int_bound and
instead document that it reads and relies on thread_var_, var_to_buffer_, and
the GetThreadConstIntBound(...) helper; specifically replace mentions of
analyzer_->const_int_bound with GetThreadConstIntBound(...) and briefly state
the expected contract/return (e.g., that it provides per-thread constant integer
bounds) so the doc matches the implementation.

391-403: Optional: short-circuit traversal once has_reducer_info is set.

ReducerInfoDetector keeps visiting sibling blocks after the flag flips to true. Since the detector only needs existence, early-returning from VisitStmt avoids unnecessary traversal of large function bodies.

♻️ Proposed micro-optimization
 class ReducerInfoDetector : public StmtVisitor {
 public:
   bool has_reducer_info = false;

 private:
+  void VisitStmt(const Stmt &stmt) final {
+    if (has_reducer_info) return;
+    StmtVisitor::VisitStmt(stmt);
+  }
+
   void VisitStmt_(const BlockNode *op) final {
     if (op->annotations.count(attr::kReducerInfo)) {
       has_reducer_info = true;
       return;
     }
     StmtVisitor::VisitStmt_(op);
   }
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/transform/layout_reducer.cc` around lines 391 - 403, ReducerInfoDetector
currently continues traversing even after has_reducer_info is true; modify its
VisitStmt_ override(s) (at least VisitStmt_(const BlockNode *op)) to
early-return immediately when has_reducer_info is true before examining
annotations or recursing via StmtVisitor::VisitStmt_, and apply the same guard
in any other VisitStmt_/visitor overrides in ReducerInfoDetector to prevent
further recursion once the flag is set.
cmake/find_pip_cuda.py (2)

186-210: Optional: emit diagnostics to stderr on failure.

Both sys.exit(1) paths (no cu_dir, no nvcc) exit silently. The caller in cmake/FindPipCUDAToolkit.cmake only sees a non-zero exit code, which loses the root cause (no candidate cu* dir found vs. cu dir found but missing nvcc). A one-line print(..., file=sys.stderr) would make auto-detect failures debuggable without changing the JSON contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/find_pip_cuda.py` around lines 186 - 210, The two silent failure exits
in main() should emit a short diagnostic to stderr before exiting: when
_find_cu_dir() returns None print a one-line message (e.g. "no CUDA directory
candidate found") to sys.stderr, and when _find_nvcc(cu_dir) returns None print
a one-line message (e.g. "nvcc not found under <cu_dir>") to sys.stderr; update
the main() function to call print(..., file=sys.stderr) with these messages
immediately prior to the existing sys.exit(1) calls so the JSON output contract
is unchanged but failures are debuggable via stderr.

87-120: Optional: simplify the generator-in-closure pattern.

add is defined as a generator (has yield) that emits at most one item, consumed via for candidate in add(...). It works, but yielding inside contextlib.suppress(OSError) obscures intent and makes control flow harder to follow. A non-generator helper is easier to read.

♻️ Proposed refactor
 def _candidate_nvidia_dirs():
     seen: set[pathlib.Path] = set()

-    def add(path: pathlib.Path):
-        with contextlib.suppress(OSError):
-            resolved = path.resolve()
-            if resolved not in seen:
-                seen.add(resolved)
-                yield resolved
+    def add(path: pathlib.Path) -> pathlib.Path | None:
+        try:
+            resolved = path.resolve()
+        except OSError:
+            return None
+        if resolved in seen:
+            return None
+        seen.add(resolved)
+        return resolved

     try:
         import nvidia
     except ImportError:
         pass
     else:
-        for candidate in add(pathlib.Path(nvidia.__path__[0])):
+        candidate = add(pathlib.Path(nvidia.__path__[0]))
+        if candidate:
             yield candidate

     for entry in sys.path:
         if not entry:
             continue
-        for candidate in add(pathlib.Path(entry) / "nvidia"):
+        candidate = add(pathlib.Path(entry) / "nvidia")
+        if candidate:
             yield candidate
     # ... and similarly for site/user-site blocks
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/find_pip_cuda.py` around lines 87 - 120, The helper add inside
_candidate_nvidia_dirs is implemented as a single-item generator which obscures
control flow; change add(path: pathlib.Path) from a generator to a regular
helper that resolves the path inside contextlib.suppress(OSError) and returns
the resolved pathlib.Path or None, then replace usages `for candidate in
add(...)` with a simple check like `candidate = add(...)` followed by `if
candidate: yield candidate`; keep all callers (the nvidia.__path__ branch,
sys.path loop, site.getsitepackages() and getusersitepackages() branches) using
the returned value and yielding when non-None to preserve behavior.
tilelang/jit/adapter/libgen.py (1)

152-158: Optional: specify encoding/errors for robust capture on Windows.

text=True without encoding/errors uses locale.getpreferredencoding() with errors='strict'. On a Windows console configured for a non-UTF-8 code page (e.g., cp936/cp1252), any non-ASCII byte in NVCC output (such as a localized error string or a path with non-ASCII chars) will raise UnicodeDecodeError inside subprocess.run, which the outer except Exception then reports as "Compile kernel failed because of UnicodeDecodeError..." — hiding the real compiler error.

♻️ Proposed hardening
             ret = subprocess.run(
                 command,
                 timeout=timeout,
                 env=compiler_env,
                 capture_output=True,
                 text=True,
+                encoding="utf-8",
+                errors="replace",
             )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/jit/adapter/libgen.py` around lines 152 - 158, The subprocess.run
invocation that assigns ret (the call using command, timeout, env=compiler_env,
capture_output=True, text=True) can raise UnicodeDecodeError on Windows; update
that call to explicitly pass encoding and errors (e.g., encoding='utf-8' and
errors='replace' or errors='surrogateescape') so decoder falls back instead of
throwing, and keep using ret.stdout/ret.stderr for logging; modify the
subprocess.run call in libgen.py where ret is set to include these parameters.
cmake/pypi-z3/FindZ3.cmake (2)

36-40: Dead if(WIN32)/else branches — both call the same add_library.

Both sides of the conditional create z3::libz3 identically, so the whole if(WIN32)/else/endif is a no-op wrapper. Simplify to a single line.

♻️ Proposed cleanup
-if(WIN32)
-    add_library(z3::libz3 SHARED IMPORTED GLOBAL)
-else()
-    add_library(z3::libz3 SHARED IMPORTED GLOBAL)
-endif()
+add_library(z3::libz3 SHARED IMPORTED GLOBAL)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/pypi-z3/FindZ3.cmake` around lines 36 - 40, The if(WIN32)/else block is
redundant because both branches call the same add_library(z3::libz3 SHARED
IMPORTED GLOBAL); remove the conditional and replace it with a single call to
add_library with the identifier z3::libz3 (keep the SHARED IMPORTED GLOBAL
properties intact) so the unnecessary if/else wrapper around add_library is
eliminated.

19-30: Z3_IMPORT_LIBRARY initial path is misleading; _z3_import_dir is never actually populated.

The initial set(Z3_IMPORT_LIBRARY "${_z3_import_dir}/libz3.lib") on line 22 points to ${CMAKE_BINARY_DIR}/z3-import/libz3.lib, but tilelang_generate_windows_import_library (defined in CMakeLists.txt) ignores that location and writes the generated .lib to ${CMAKE_BINARY_DIR}/windows-import-libs/libz3/<dllname>/libz3.lib, then overwrites Z3_IMPORT_LIBRARY via its OUTPUT_VAR. Net effects:

  • The file(MAKE_DIRECTORY "${_z3_import_dir}") call creates an always-empty dir that's never used.
  • The if(NOT EXISTS "${Z3_IMPORT_LIBRARY}") guard is effectively always true on reconfigures (the file-not-found guard inside tilelang_generate_windows_import_library itself handles re-run caching).

Either drop the pre-seeding entirely and let the helper own the path, or ask the helper to write to the expected location. Minor cleanup, not a correctness bug.

♻️ Proposed simplification
     set(Z3_DLL "${Z3_LIBRARY}")
     if(Z3_DLL)
-        set(_z3_import_dir "${CMAKE_BINARY_DIR}/z3-import")
-        file(MAKE_DIRECTORY "${_z3_import_dir}")
-        set(Z3_IMPORT_LIBRARY "${_z3_import_dir}/libz3.lib")
-        if(NOT EXISTS "${Z3_IMPORT_LIBRARY}")
-            if(COMMAND tilelang_generate_windows_import_library)
-                tilelang_generate_windows_import_library("${Z3_DLL}" Z3_IMPORT_LIBRARY "libz3")
-            else()
-                message(FATAL_ERROR "tilelang_generate_windows_import_library is unavailable for Windows Z3 import-lib generation")
-            endif()
-        endif()
+        if(COMMAND tilelang_generate_windows_import_library)
+            tilelang_generate_windows_import_library("${Z3_DLL}" Z3_IMPORT_LIBRARY "libz3")
+        else()
+            message(FATAL_ERROR "tilelang_generate_windows_import_library is unavailable for Windows Z3 import-lib generation")
+        endif()
     endif()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/pypi-z3/FindZ3.cmake` around lines 19 - 30, The Z3 import-lib
pre-seeding is misleading because _z3_import_dir and Z3_IMPORT_LIBRARY are set
but never used by tilelang_generate_windows_import_library; remove the unused
pre-seed and directory creation or defer to the helper: delete the
file(MAKE_DIRECTORY "${_z3_import_dir}") and the set(Z3_IMPORT_LIBRARY
"${_z3_import_dir}/libz3.lib") / if(NOT EXISTS ...) block and instead call
tilelang_generate_windows_import_library when Z3_DLL is set so that the helper
sets Z3_IMPORT_LIBRARY via its OUTPUT_VAR (or alternatively update
tilelang_generate_windows_import_library to accept an explicit output path and
write to _z3_import_dir), referencing the symbols Z3_DLL, _z3_import_dir,
Z3_IMPORT_LIBRARY, and tilelang_generate_windows_import_library to locate code
to change.
src/target/stubs/nvrtc.cc (1)

32-45: Consider adding nvrtc64_140_0.dll / future CUDA 14 to the Windows candidate list.

The list tops out at nvrtc64_130_0.dll (CUDA 13.x). Once CUDA 14 ships, this stub silently fails to locate the installed library on machines that only have CUDA 14 until the list is updated. Low priority, just a maintenance reminder — you could prepend "nvrtc64_140_0.dll" now, or add a comment pointing maintainers here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/target/stubs/nvrtc.cc` around lines 32 - 45, The Windows nvRTC candidate
array kLibNvrtcPaths in nvrtc.cc stops at CUDA 13 names so systems with CUDA 14
may fail to load; update kLibNvrtcPaths to include "nvrtc64_140_0.dll"
(preferably prepended to the Windows list) or add a clear TODO comment above
kLibNvrtcPaths noting to add future nvrtc64_XXX_0.dll entries for new CUDA major
versions so the loader finds newer libraries without silent failures.
scripts/windows/uv-sync.cmd (1)

8-8: Use %~1 for the terminator check to handle quoted arguments robustly.

If a passthrough argument is itself a quoted string (e.g., --config-setting "cmake.args=-DFOO=bar"), %1 keeps the surrounding quotes, and the "%1"=="" form compares ""<quoted>"" == "" — which is still false, so this specific check works. However, using %~1 (which strips surrounding quotes) is the conventional and more resilient pattern and matches your comparison style on line 9.

🔧 Proposed fix
-if "%1"=="" goto args_done
+if "%~1"=="" goto args_done
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/windows/uv-sync.cmd` at line 8, Replace the bare %1 check with the
quote-stripped form so the terminator comparison handles quoted passthrough
arguments correctly: change the conditional that compares "%1" to "" to use %~1
(the stripped-quote form) in the if test that jumps to the args_done label (the
existing if ... goto args_done check), ensuring the check uses %~1 for
robustness with quoted arguments.
tilelang/__init__.py (3)

125-126: Silent except Exception: pass swallows failures from find_libtvm_ffi.

Static analysis (Ruff S110/BLE001) flagged this. Since tvm_ffi.libinfo.find_libtvm_ffi() failing means we won't prepend that runtime path on Windows — and the subsequent import tvm may then fail with an opaque DLL load failed, it's worth at least a logger.debug so the cause is discoverable.

🔧 Proposed logging
     try:
         from tvm_ffi import libinfo as tvm_ffi_libinfo

         runtime_paths.append(os.path.dirname(tvm_ffi_libinfo.find_libtvm_ffi()))
-    except Exception:
-        pass
+    except Exception:  # noqa: BLE001
+        logger.debug("Failed to resolve tvm_ffi runtime path", exc_info=True)

Note: logger is defined at line 52 which runs before this helper is invoked, so it's safe to reference.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/__init__.py` around lines 125 - 126, The except block swallowing all
exceptions around tvm_ffi.libinfo.find_libtvm_ffi() should log the exception so
failures are discoverable; update the except Exception: pass in
tilelang/__init__.py to catch Exception as e and call logger.debug (or
logger.exception) with a clear message referencing find_libtvm_ffi and the
exception, so if the runtime path isn't prepended and import tvm fails the root
cause is visible (logger is available at module scope).

177-191: Duplicates the CUDA path list in libinfo.get_dll_directories().

The tuple of cuda_home subpaths (cuda_home, bin, bin/x86_64, lib/x64, nvvm/bin) is the same one built by tilelang/libinfo.py::get_dll_directories() (which you already call below in _load_tile_lang_lib). Two copies mean any future change to the discovery order has to be updated in both places.

Since get_dll_directories() can be called before import tvm (it only depends on tilelang.env and tilelang.libinfo, which are already imported), you could reuse it here:

♻️ Proposed DRY refactor
         if sys.platform.startswith("win32") and sys.version_info >= (3, 8):
             _prepend_windows_runtime_paths()
+            from . import libinfo as _libinfo  # noqa: E402
             _dll_handles = []
-            cuda_home = getattr(env, "CUDA_HOME", "")
-            if cuda_home:
-                for path in (
-                    cuda_home,
-                    os.path.join(cuda_home, "bin"),
-                    os.path.join(cuda_home, "bin", "x86_64"),
-                    os.path.join(cuda_home, "lib", "x64"),
-                    os.path.join(cuda_home, "nvvm", "bin"),
-                ):
-                    if os.path.isdir(path):
-                        _dll_handles.append(os.add_dll_directory(path))
+            for path in _libinfo.get_dll_directories():
+                _dll_handles.append(os.add_dll_directory(path))

Note: get_dll_directories() also adds TL_LIBS, which is desirable here (TVM DLLs live under tilelang/3rdparty/tvm). If you want to keep this pre-tvm-import step scoped to CUDA only, factor the CUDA-only list into a separate helper in libinfo.py.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/__init__.py` around lines 177 - 191, The CUDA DLL directory list is
duplicated here; instead of rebuilding the same tuple, call
tilelang.libinfo.get_dll_directories() (or add a small helper in
tilelang.libinfo like get_cuda_dll_directories() if you want CUDA-only behavior)
from the __init__.py block that runs on Windows (the code using
_prepend_windows_runtime_paths, _dll_handles, cuda_home); use the returned
iterable to iterate and call os.add_dll_directory for each existing path, and
remove the hard-coded tuple so discovery logic is centralized and not duplicated
(also keep the existing use of _load_tile_lang_lib which already relies on
libinfo.get_dll_directories()).

54-57: Mutating os.RTLD_LAZY at import time affects the entire process.

This assignment to os.RTLD_LAZY = 0 on Windows is visible to every other module in the interpreter. It's safe because os doesn't define it on Windows, but any third-party code that checks hasattr(os, 'RTLD_LAZY') to branch on POSIX-vs-Windows will now see it as truthy. An alternative would be to read getattr(os, "RTLD_LAZY", 0) inside _lazy_load_lib (which already guards with hasattr at line 153) rather than mutating the os module.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/__init__.py` around lines 54 - 57, The file currently sets
os.RTLD_LAZY = 0 on Windows which mutates the global os module; instead remove
that import-time assignment and update the loader to use a local fallback flag
(e.g., read rtld_flag = getattr(os, "RTLD_LAZY", 0)) when building the CDLL
flags inside the _lazy_load_lib function so you no longer inject RTLD_LAZY into
os; locate references to os.RTLD_LAZY and replace them with a local getattr
fallback in _lazy_load_lib (or compute a module-level constant like _RTLD_LAZY =
getattr(os, "RTLD_LAZY", 0) without assigning to os) to preserve current
behavior without mutating the os module.
tilelang/contrib/cc.py (1)

54-60: Duplicated Windows MSVC discovery across get_cc and get_cplus_compiler.

The Windows branch in both functions is byte-identical. Consider extracting a small helper (e.g., _find_msvc_cl()) to keep them in sync and simplify future changes.

♻️ Proposed helper extraction
+def _find_msvc_cl():
+    from tilelang.contrib.msvc import get_msvc_subprocess_env, get_env_path
+    compiler_env = get_msvc_subprocess_env()
+    return shutil.which("cl.exe", path=get_env_path(compiler_env or {}))
+
+
 def get_cc():
     ...
     if _is_windows_like():
-        from tilelang.contrib.msvc import get_msvc_subprocess_env, get_env_path
-
-        compiler_env = get_msvc_subprocess_env()
-        cl = shutil.which("cl.exe", path=get_env_path(compiler_env or {}))
-        return cl
+        return _find_msvc_cl()

Also applies to: 85-91

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/contrib/cc.py` around lines 54 - 60, The Windows-specific MSVC
discovery logic duplicated in get_cc and get_cplus_compiler should be extracted
into a helper (e.g., _find_msvc_cl) that performs the same steps: import
get_msvc_subprocess_env and get_env_path from tilelang.contrib.msvc, call
get_msvc_subprocess_env(), compute the env path via get_env_path(compiler_env or
{}), locate "cl.exe" with shutil.which(..., path=...), and return the discovered
path; then replace the duplicated blocks in get_cc and get_cplus_compiler with a
call to _find_msvc_cl() so behavior remains identical and both functions stay in
sync.
tilelang/libinfo.py (1)

49-56: Minor: simplify the redundant for…else and clarify the error message.

The outer-loop else fires only when no return occurred, which is exactly the same as falling through. The message also calls TL_LIBS the "List of candidates", but semantically those are search directories (candidates are the full lib_root/lib_name combinations).

♻️ Suggested simplification
-    for lib_root in TL_LIBS:
-        for lib_name in lib_names:
-            lib_dll_path = os.path.join(lib_root, lib_name)
-            if os.path.exists(lib_dll_path) and os.path.isfile(lib_dll_path):
-                return lib_dll_path
-    else:
-        message = f"Cannot find libraries: {', '.join(lib_names)}\n" + "List of candidates:\n" + "\n".join(TL_LIBS)
-        raise RuntimeError(message)
+    for lib_root in TL_LIBS:
+        for lib_name in lib_names:
+            lib_dll_path = os.path.join(lib_root, lib_name)
+            if os.path.isfile(lib_dll_path):
+                return lib_dll_path
+    raise RuntimeError(
+        f"Cannot find libraries: {', '.join(lib_names)}\n"
+        f"Searched in: {', '.join(TL_LIBS)}"
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/libinfo.py` around lines 49 - 56, The nested for…else is redundant:
iterate TL_LIBS and lib_names, return lib_dll_path on first existing file, and
if none found raise a RuntimeError after both loops; remove the for...else
construct and place the error raise after the loops. Update the error message to
list the full candidate paths (each os.path.join(lib_root, lib_name)) instead of
calling TL_LIBS "List of candidates", referencing TL_LIBS, lib_names and
lib_dll_path so the message shows the exact searched file paths.
tilelang/contrib/msvc.py (2)

174-179: Minor: mark the MD5 hash as non-security.

Ruff S324 will flag this on every CI run. The hash is used purely to disambiguate patched filenames, so pass usedforsecurity=False (Python 3.9+) to document intent and silence the warning.

-    prefix = hashlib.md5(os.path.dirname(os.path.abspath(path)).encode()).hexdigest()[:8]
+    prefix = hashlib.md5(
+        os.path.dirname(os.path.abspath(path)).encode(),
+        usedforsecurity=False,
+    ).hexdigest()[:8]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/contrib/msvc.py` around lines 174 - 179, The MD5 call that generates
prefix (variable prefix via hashlib.md5(...).hexdigest()) should explicitly mark
the hash as non-security by passing usedforsecurity=False to hashlib.md5 to
silence Ruff S324 and document intent; update the hashlib.md5 invocation in this
function/block that computes prefix so it becomes hashlib.md5(<data>,
usedforsecurity=False) (Python 3.9+).

275-287: subprocess.Popen with no timeout can hang the build.

If cl.exe (or the linker spawned by it) deadlocks — which does happen on Windows with certain antivirus/filesystem interactions — the build will hang indefinitely with no way to recover short of killing the parent. Consider proc.communicate(timeout=…) with a generous timeout (e.g., 30 min) or use subprocess.run(..., timeout=...) and surface a clearer error.

♻️ Optional hardening
-        proc = subprocess.Popen(
-            cmd,
-            stdout=subprocess.PIPE,
-            stderr=subprocess.STDOUT,
-            cwd=cwd,
-            env=compiler_env,
-        )
-        out, _ = proc.communicate()
+        proc = subprocess.Popen(
+            cmd,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.STDOUT,
+            cwd=cwd,
+            env=compiler_env,
+        )
+        try:
+            out, _ = proc.communicate(timeout=30 * 60)
+        except subprocess.TimeoutExpired:
+            proc.kill()
+            out, _ = proc.communicate()
+            raise RuntimeError(
+                "MSVC compilation timed out after 30 minutes.\n"
+                "Command line: " + " ".join(cmd)
+            )
         if proc.returncode != 0:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/contrib/msvc.py` around lines 275 - 287, The current subprocess
invocation in msvc.py uses subprocess.Popen and proc.communicate() with no
timeout, which can hang; modify the call in the block that creates proc (the
variables cmd, cwd, compiler_env and the proc variable) to use a timeout (e.g.,
communicate(timeout=1800) or replace with subprocess.run(..., timeout=1800,
capture_output=True)) and catch subprocess.TimeoutExpired to kill/terminate the
child process, collect any partial output (use proc.stdout/communicate after
terminate if needed), and raise a RuntimeError that includes the timeout
indication plus any captured output and the command line (same message structure
as the existing RuntimeError using py_str(out) and " ".join(cmd)). Ensure the
timeout handling is applied around the proc.communicate()/subprocess.run call
and that processes are properly terminated to avoid zombie processes.
cmake/FindPipCUDAToolkit.cmake (1)

168-258: Minor: _tilelang_sdk_bin is prepended to ENV{PATH} twice within one configure.

Line 170 adds _tilelang_sdk_bin to $ENV{PATH} once, then lines 243–258 re-prepend system path + re-prepend _tilelang_sdk_bin a second time. End result: _tilelang_sdk_bin appears twice in PATH. Harmless but noisy and makes find_program diagnostics harder to read.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/FindPipCUDAToolkit.cmake` around lines 168 - 258, The _tilelang_sdk_bin
path is being prepended to ENV{PATH} twice via the early set(ENV{PATH}
"${_tilelang_sdk_bin};$ENV{PATH}") and again near the end; remove the first
prepend (the one immediately after the message(STATUS) call) or guard both with
a conditional that checks whether "$ENV{PATH}" already MATCHES
"(^|;)\Q${_tilelang_sdk_bin}\E($|;)" so _tilelang_sdk_bin is only added once;
key symbols: _tilelang_sdk_bin, ENV{PATH},
_tilelang_system_path_prefix/_tilelang_system_path_candidates.
CMakeLists.txt (1)

28-78: Import-library helper looks solid.

Good structure overall — per-DLL subdirectory keying (Line 39) makes the on-disk .lib cache self-invalidate when the source DLL's basename changes, the machine-type mapping covers the realistic MSVC generator platforms, and the tool-missing / non-zero exit paths fail loudly with actionable messages. The if(NOT EXISTS "${_tilelang_import_lib}") short-circuit (Line 42) is a nice configure-time perf win.

Two very small suggestions, both optional:

  • Line 42: if the .def exists but the .lib doesn't (e.g., previous run was killed between the two execute_process calls), you skip regenerating the .def but still run lib.exe. That's actually fine, but consider caching via the .lib timestamp vs. the source DLL's timestamp if you want to rebuild when the DLL is updated in place.
  • Line 63-67: CMAKE_GENERATOR_PLATFORM is only set for multi-config Visual Studio generators; Ninja users on ARM64 would get the X64 default. If ARM64 Windows is a supported target, consider also consulting CMAKE_SYSTEM_PROCESSOR / CMAKE_HOST_SYSTEM_PROCESSOR as a fallback.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CMakeLists.txt` around lines 28 - 78, The
tilelang_generate_windows_import_library helper should (1) regenerate the .def
when the source DLL is newer than the cached .def (avoid a stale .def when a
previous run was interrupted): compare timestamps of "${DLL_PATH}" vs
"${_tilelang_def_file}" and run the generate step if the DLL is newer or the
.def is missing before invoking lib.exe (refer to _tilelang_def_file and
_tilelang_import_lib), and (2) make the machine selection more robust by falling
back to CMAKE_SYSTEM_PROCESSOR / CMAKE_HOST_SYSTEM_PROCESSOR when
CMAKE_GENERATOR_PLATFORM is unset so ARM64 Ninja builds get "ARM64" instead of
defaulting to "X64" (update the block that sets _tilelang_machine and checks
CMAKE_GENERATOR_PLATFORM to consult those fallback variables).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmake/find_pip_cuda.py`:
- Around line 177-184: The _library_dir function returns cu_dir/"lib"/"x64" on
Windows without checking existence, causing missing paths to flow into
CMAKE_LIBRARY_PATH; update _library_dir to check that cu_dir/"lib"/"x64" exists
when os.name == "nt" (similar to the lib64 is_dir() check on Linux) and if it
doesn't exist either try alternative paths or raise/return a clear error/None so
the caller (_tilelang_run_find_pip_cuda / FindPipCUDAToolkit logic) can surface
a descriptive message rather than propagating a non-existent path; reference the
_library_dir function and the cu_dir/"lib"/"x64" and cu_dir/"lib64" path checks
when making the change.

In `@cmake/FindPipCUDAToolkit.cmake`:
- Around line 276-286: The linker /LIBPATH flags are being prepended into the
cached CMAKE_{EXE,SHARED,MODULE}_LINKER_FLAGS on every configure via
${_tilelang_libpath_flags}, causing unbounded growth; modify the logic (around
variables _tilelang_libpath_flags and the CMAKE_*_LINKER_FLAGS cache sets) to be
idempotent—either add a guard variable like _TILELANG_LINK_FLAGS_APPLIED
(mirroring _TILELANG_INCLUDE_FLAGS_APPLIED) or check whether the cached flags
already contain the /LIBPATH entries before setting; only set the cached
CMAKE_EXE_LINKER_FLAGS, CMAKE_SHARED_LINKER_FLAGS and CMAKE_MODULE_LINKER_FLAGS
once (or append only missing entries) so repeated cmake re-configures do not
duplicate /LIBPATH fragments.

In `@CMakeLists.txt`:
- Around line 383-394: Update the NVRTC discovery block to use the documented
CUDAToolkit_BIN_DIR instead of CUDAToolkit_ROOT: change the file(GLOB ...)
patterns to use "${CUDAToolkit_BIN_DIR}/bin/..." (refer to the if(WIN32 AND NOT
TILELANG_USE_CUDA_STUBS) block and the _tilelang_nvrtc_candidates variable),
keep the existing list(SORT ...), list(GET ... _tilelang_nvrtc_library) and
tilelang_generate_windows_import_library(...) logic, and if no
_tilelang_nvrtc_candidates are found emit a status message (e.g., message(STATUS
"... no NVRTC DLL found, continuing without setting CUDA_NVRTC_LIBRARY")) so
callers know fallback discovery will run.
- Around line 570-601: On Windows the tilelang objects are archived into
tilelang_archive and linked into tvm.dll, but because tvm is marked
EXCLUDE_FROM_ALL it may not get built during a plain cmake --build ., leaving
tilelang_cython_wrapper to fail at runtime; add a build dependency so tvm.dll is
always built for local dev builds by adding
add_dependencies(tilelang_cython_wrapper tvm) (or alternatively ensure a
tilelang SHARED target is created on Windows and depends on tvm) so the tvm
target is built before tilelang_cython_wrapper is built.

In `@pyproject.toml`:
- Line 108: The global setting cmake.args = ["-G", "Ninja"] forces Ninja
everywhere and overrides CMAKE_GENERATOR; remove this unconditional entry from
pyproject.toml and instead only set the Ninja generator for Windows
builds—either by configuring CMAKE_GENERATOR in your CI (cibuildwheel
environment-pass) or by applying a platform-conditional override so that
cmake.args (the "-G", "Ninja" pair) is only used when sys_platform == "win32"
(or when building with MSVC); keep other platforms relying on CMAKE_GENERATOR or
scikit-build-core's selection.

In `@src/target/codegen_cuda.cc`:
- Around line 3591-3595: The tl::__tan() branch currently uses CUDAFastMath
(variable CUDAFastMath math_func) which emits lower-precision __tanf for
float32; switch this to use CUDAFastMathTan instead so the tangent call
preserves the higher-precision implementation already defined (i.e., replace
CUDAFastMath usage with CUDAFastMathTan in the tl::__tan() handling and call its
function-name generator the same way you did previously to produce the correct
tan wrapper).

In `@src/target/stubs/dynlib.h`:
- Around line 104-107: The header-level definition of _GNU_SOURCE in dynlib.h is
too late for headers included earlier (breaking RTLD_DEFAULT/RTLD_NEXT usage in
dynlib_find_loaded); remove the `#ifndef/_GNU_SOURCE/`#endif block from dynlib.h
and instead add a project-wide compile definition via CMake for the affected
stub targets (use target_compile_definitions(<stub-target> PRIVATE
_GNU_SOURCE=1) for the targets that build the stubs so _GNU_SOURCE is defined
before any system headers are processed), then rebuild to ensure
dynlib_find_loaded() sees RTLD_DEFAULT and RTLD_NEXT.

In `@src/transform/layout_inference.cc`:
- Around line 756-761: The code currently assumes attr::kLayoutMap is Map<Var,
Layout> and ICHECKs that, which will abort when the annotation is a different
map type (e.g., Map<Buffer, Layout> written by this pass); change the logic in
the block that reads op->annotations.Get(attr::kLayoutMap) so it attempts to
as<Map<Var, Layout>>() and if map_opt.has_value() is false simply skip/ignore
the annotation (do not ICHECK/abort) — i.e., only proceed to use the result when
map_opt has a value, otherwise return/continue from the function so
already-materialized layout maps are safely ignored; keep references to
attr::kLayoutMap, op->annotations.Get, and the Map<Var, Layout> cast to locate
the change.

In `@tilelang/contrib/cc.py`:
- Around line 54-60: get_cc() currently performs MSVC discovery at import and
isn't cached, causing slow shell-out on Windows and repeated probes; add
functools.cache to get_cc() (matching get_cplus_compiler()) and change the eager
default usage in create_shared.get_target_triple from os.environ.get("CXX",
get_cc()) to os.environ.get("CXX") or get_cc() so get_cc() is only invoked when
CXX is unset/empty; keep the MSVC helper imports (get_msvc_subprocess_env,
get_env_path) and shutil.which usage inside get_cc() so subprocess discovery is
deferred and subsequent calls use the cache.

In `@tilelang/contrib/msvc.py`:
- Around line 113-145: In get_msvc_subprocess_env(), when VS setup fails (no
vsdevcmd found, _import_vsdevcmd_environment returns None, or the resulting
compiler_env still lacks cl.exe), set _MSVC_ENV_ERROR as you're already doing
but change the cached result to None (set _MSVC_ENV_CACHE = None) and return
None instead of returning base_env; keep the early fast-path that returns
base_env when cl.exe is already present in the unmodified environment. Update
the failure branches that reference _find_vsdevcmd,
_import_vsdevcmd_environment, and the final shutil.which(check with
get_env_path(compiler_env)) to follow this behavior so callers can distinguish
“no MSVC setup” via a None return while still exposing errors via
get_msvc_environment_error().

---

Outside diff comments:
In `@tilelang/jit/adapter/wrapper.py`:
- Around line 60-86: The CPU backend's exported call symbol is not marked with
TL_EXPORT, causing missing export on Windows; change the CPU wrapper emission so
the call signature uses the same export macro as CUDA/HIP by replacing the plain
'extern "C" int32_t call(...)' emitted by TLCPUSourceWrapper.CALL_PREFIX with
the TL_EXPORT-enabled prefix (as defined by PREDEF_HOST_FUNC / PREDEF_INIT_FUNC
where TL_EXPORT is already defined), ensuring the CPU shared lib exposes call
and the ctypes adapter can resolve self.lib.call.

---

Nitpick comments:
In `@cmake/find_pip_cuda.py`:
- Around line 186-210: The two silent failure exits in main() should emit a
short diagnostic to stderr before exiting: when _find_cu_dir() returns None
print a one-line message (e.g. "no CUDA directory candidate found") to
sys.stderr, and when _find_nvcc(cu_dir) returns None print a one-line message
(e.g. "nvcc not found under <cu_dir>") to sys.stderr; update the main() function
to call print(..., file=sys.stderr) with these messages immediately prior to the
existing sys.exit(1) calls so the JSON output contract is unchanged but failures
are debuggable via stderr.
- Around line 87-120: The helper add inside _candidate_nvidia_dirs is
implemented as a single-item generator which obscures control flow; change
add(path: pathlib.Path) from a generator to a regular helper that resolves the
path inside contextlib.suppress(OSError) and returns the resolved pathlib.Path
or None, then replace usages `for candidate in add(...)` with a simple check
like `candidate = add(...)` followed by `if candidate: yield candidate`; keep
all callers (the nvidia.__path__ branch, sys.path loop, site.getsitepackages()
and getusersitepackages() branches) using the returned value and yielding when
non-None to preserve behavior.

In `@cmake/FindPipCUDAToolkit.cmake`:
- Around line 168-258: The _tilelang_sdk_bin path is being prepended to
ENV{PATH} twice via the early set(ENV{PATH} "${_tilelang_sdk_bin};$ENV{PATH}")
and again near the end; remove the first prepend (the one immediately after the
message(STATUS) call) or guard both with a conditional that checks whether
"$ENV{PATH}" already MATCHES "(^|;)\Q${_tilelang_sdk_bin}\E($|;)" so
_tilelang_sdk_bin is only added once; key symbols: _tilelang_sdk_bin, ENV{PATH},
_tilelang_system_path_prefix/_tilelang_system_path_candidates.

In `@cmake/pypi-z3/FindZ3.cmake`:
- Around line 36-40: The if(WIN32)/else block is redundant because both branches
call the same add_library(z3::libz3 SHARED IMPORTED GLOBAL); remove the
conditional and replace it with a single call to add_library with the identifier
z3::libz3 (keep the SHARED IMPORTED GLOBAL properties intact) so the unnecessary
if/else wrapper around add_library is eliminated.
- Around line 19-30: The Z3 import-lib pre-seeding is misleading because
_z3_import_dir and Z3_IMPORT_LIBRARY are set but never used by
tilelang_generate_windows_import_library; remove the unused pre-seed and
directory creation or defer to the helper: delete the file(MAKE_DIRECTORY
"${_z3_import_dir}") and the set(Z3_IMPORT_LIBRARY
"${_z3_import_dir}/libz3.lib") / if(NOT EXISTS ...) block and instead call
tilelang_generate_windows_import_library when Z3_DLL is set so that the helper
sets Z3_IMPORT_LIBRARY via its OUTPUT_VAR (or alternatively update
tilelang_generate_windows_import_library to accept an explicit output path and
write to _z3_import_dir), referencing the symbols Z3_DLL, _z3_import_dir,
Z3_IMPORT_LIBRARY, and tilelang_generate_windows_import_library to locate code
to change.

In `@CMakeLists.txt`:
- Around line 28-78: The tilelang_generate_windows_import_library helper should
(1) regenerate the .def when the source DLL is newer than the cached .def (avoid
a stale .def when a previous run was interrupted): compare timestamps of
"${DLL_PATH}" vs "${_tilelang_def_file}" and run the generate step if the DLL is
newer or the .def is missing before invoking lib.exe (refer to
_tilelang_def_file and _tilelang_import_lib), and (2) make the machine selection
more robust by falling back to CMAKE_SYSTEM_PROCESSOR /
CMAKE_HOST_SYSTEM_PROCESSOR when CMAKE_GENERATOR_PLATFORM is unset so ARM64
Ninja builds get "ARM64" instead of defaulting to "X64" (update the block that
sets _tilelang_machine and checks CMAKE_GENERATOR_PLATFORM to consult those
fallback variables).

In `@scripts/windows/uv-sync.cmd`:
- Line 8: Replace the bare %1 check with the quote-stripped form so the
terminator comparison handles quoted passthrough arguments correctly: change the
conditional that compares "%1" to "" to use %~1 (the stripped-quote form) in the
if test that jumps to the args_done label (the existing if ... goto args_done
check), ensuring the check uses %~1 for robustness with quoted arguments.

In `@src/runtime/logging.cc`:
- Around line 108-124: Remove the unreachable "return settings;" statements that
follow the LOG(FATAL) calls in the level-parsing block (the checks for
ill-formed token, empty level, and invalid numeric level) because LOG(FATAL)
routes to LogFatalImpl which is [[noreturn]]; keep behavior consistent with the
earlier LOG(FATAL) that already omits a trailing return and leave only the
LOG(FATAL) calls in place.
- Around line 3-4: The translation unit uses std::istringstream and
std::stringstream in ParseSpec and VLogContext::str() but doesn't include
<sstream>, relying on transitive includes; add an explicit `#include` <sstream> at
the top of the file so ParseSpec and VLogContext::str() have the required header
and the compilation is self-contained.

In `@src/target/stubs/nvrtc.cc`:
- Around line 32-45: The Windows nvRTC candidate array kLibNvrtcPaths in
nvrtc.cc stops at CUDA 13 names so systems with CUDA 14 may fail to load; update
kLibNvrtcPaths to include "nvrtc64_140_0.dll" (preferably prepended to the
Windows list) or add a clear TODO comment above kLibNvrtcPaths noting to add
future nvrtc64_XXX_0.dll entries for new CUDA major versions so the loader finds
newer libraries without silent failures.

In `@src/transform/layout_reducer.cc`:
- Around line 174-175: Update the function docstring to remove the stale
reference to analyzer_->const_int_bound and instead document that it reads and
relies on thread_var_, var_to_buffer_, and the GetThreadConstIntBound(...)
helper; specifically replace mentions of analyzer_->const_int_bound with
GetThreadConstIntBound(...) and briefly state the expected contract/return
(e.g., that it provides per-thread constant integer bounds) so the doc matches
the implementation.
- Around line 391-403: ReducerInfoDetector currently continues traversing even
after has_reducer_info is true; modify its VisitStmt_ override(s) (at least
VisitStmt_(const BlockNode *op)) to early-return immediately when
has_reducer_info is true before examining annotations or recursing via
StmtVisitor::VisitStmt_, and apply the same guard in any other
VisitStmt_/visitor overrides in ReducerInfoDetector to prevent further recursion
once the flag is set.

In `@tilelang/__init__.py`:
- Around line 125-126: The except block swallowing all exceptions around
tvm_ffi.libinfo.find_libtvm_ffi() should log the exception so failures are
discoverable; update the except Exception: pass in tilelang/__init__.py to catch
Exception as e and call logger.debug (or logger.exception) with a clear message
referencing find_libtvm_ffi and the exception, so if the runtime path isn't
prepended and import tvm fails the root cause is visible (logger is available at
module scope).
- Around line 177-191: The CUDA DLL directory list is duplicated here; instead
of rebuilding the same tuple, call tilelang.libinfo.get_dll_directories() (or
add a small helper in tilelang.libinfo like get_cuda_dll_directories() if you
want CUDA-only behavior) from the __init__.py block that runs on Windows (the
code using _prepend_windows_runtime_paths, _dll_handles, cuda_home); use the
returned iterable to iterate and call os.add_dll_directory for each existing
path, and remove the hard-coded tuple so discovery logic is centralized and not
duplicated (also keep the existing use of _load_tile_lang_lib which already
relies on libinfo.get_dll_directories()).
- Around line 54-57: The file currently sets os.RTLD_LAZY = 0 on Windows which
mutates the global os module; instead remove that import-time assignment and
update the loader to use a local fallback flag (e.g., read rtld_flag =
getattr(os, "RTLD_LAZY", 0)) when building the CDLL flags inside the
_lazy_load_lib function so you no longer inject RTLD_LAZY into os; locate
references to os.RTLD_LAZY and replace them with a local getattr fallback in
_lazy_load_lib (or compute a module-level constant like _RTLD_LAZY = getattr(os,
"RTLD_LAZY", 0) without assigning to os) to preserve current behavior without
mutating the os module.

In `@tilelang/contrib/cc.py`:
- Around line 54-60: The Windows-specific MSVC discovery logic duplicated in
get_cc and get_cplus_compiler should be extracted into a helper (e.g.,
_find_msvc_cl) that performs the same steps: import get_msvc_subprocess_env and
get_env_path from tilelang.contrib.msvc, call get_msvc_subprocess_env(), compute
the env path via get_env_path(compiler_env or {}), locate "cl.exe" with
shutil.which(..., path=...), and return the discovered path; then replace the
duplicated blocks in get_cc and get_cplus_compiler with a call to
_find_msvc_cl() so behavior remains identical and both functions stay in sync.

In `@tilelang/contrib/msvc.py`:
- Around line 174-179: The MD5 call that generates prefix (variable prefix via
hashlib.md5(...).hexdigest()) should explicitly mark the hash as non-security by
passing usedforsecurity=False to hashlib.md5 to silence Ruff S324 and document
intent; update the hashlib.md5 invocation in this function/block that computes
prefix so it becomes hashlib.md5(<data>, usedforsecurity=False) (Python 3.9+).
- Around line 275-287: The current subprocess invocation in msvc.py uses
subprocess.Popen and proc.communicate() with no timeout, which can hang; modify
the call in the block that creates proc (the variables cmd, cwd, compiler_env
and the proc variable) to use a timeout (e.g., communicate(timeout=1800) or
replace with subprocess.run(..., timeout=1800, capture_output=True)) and catch
subprocess.TimeoutExpired to kill/terminate the child process, collect any
partial output (use proc.stdout/communicate after terminate if needed), and
raise a RuntimeError that includes the timeout indication plus any captured
output and the command line (same message structure as the existing RuntimeError
using py_str(out) and " ".join(cmd)). Ensure the timeout handling is applied
around the proc.communicate()/subprocess.run call and that processes are
properly terminated to avoid zombie processes.

In `@tilelang/jit/adapter/libgen.py`:
- Around line 152-158: The subprocess.run invocation that assigns ret (the call
using command, timeout, env=compiler_env, capture_output=True, text=True) can
raise UnicodeDecodeError on Windows; update that call to explicitly pass
encoding and errors (e.g., encoding='utf-8' and errors='replace' or
errors='surrogateescape') so decoder falls back instead of throwing, and keep
using ret.stdout/ret.stderr for logging; modify the subprocess.run call in
libgen.py where ret is set to include these parameters.

In `@tilelang/libinfo.py`:
- Around line 49-56: The nested for…else is redundant: iterate TL_LIBS and
lib_names, return lib_dll_path on first existing file, and if none found raise a
RuntimeError after both loops; remove the for...else construct and place the
error raise after the loops. Update the error message to list the full candidate
paths (each os.path.join(lib_root, lib_name)) instead of calling TL_LIBS "List
of candidates", referencing TL_LIBS, lib_names and lib_dll_path so the message
shows the exact searched file paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a0964f23-707e-4b99-8a1d-ed07241fe60c

📥 Commits

Reviewing files that changed from the base of the PR and between 0f29f9c and e876998.

📒 Files selected for processing (35)
  • .gitattributes
  • .gitignore
  • CMakeLists.txt
  • cmake/FindPipCUDAToolkit.cmake
  • cmake/find_pip_cuda.py
  • cmake/generate_windows_import_lib.py
  • cmake/pypi-z3/FindZ3.cmake
  • docs/get_started/Installation.md
  • pyproject.toml
  • scripts/windows/uv-sync.cmd
  • src/layout/gemm_layouts.cc
  • src/op/operator.h
  • src/op/parallel.cc
  • src/runtime/logging.cc
  • src/target/codegen_cuda.cc
  • src/target/codegen_cuda.h
  • src/target/stubs/cuda.cc
  • src/target/stubs/cuda.h
  • src/target/stubs/cudart.cc
  • src/target/stubs/dynlib.h
  • src/target/stubs/nvrtc.cc
  • src/transform/common/pipeline_utils.h
  • src/transform/layout_inference.cc
  • src/transform/layout_reducer.cc
  • tilelang/__init__.py
  • tilelang/cache/kernel_cache.py
  • tilelang/carver/arch/driver/cuda_driver.py
  • tilelang/contrib/cc.py
  • tilelang/contrib/msvc.py
  • tilelang/contrib/nvcc.py
  • tilelang/env.py
  • tilelang/jit/adapter/libgen.py
  • tilelang/jit/adapter/tvm_ffi.py
  • tilelang/jit/adapter/wrapper.py
  • tilelang/libinfo.py

Comment thread cmake/find_pip_cuda.py
Comment thread cmake/FindPipCUDAToolkit.cmake
Comment thread CMakeLists.txt Outdated
Comment thread CMakeLists.txt
Comment thread pyproject.toml Outdated
Comment thread src/backend/cuda/codegen/codegen_cuda.cc
Comment thread src/target/stubs/dynlib.h Outdated
Comment thread src/transform/layout_inference.cc Outdated
Comment thread tilelang/contrib/cc.py
Comment thread tilelang/contrib/msvc.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tilelang/jit/adapter/wrapper.py (1)

751-758: ⚠️ Potential issue | 🟡 Minor

CPU backend call is missing the export annotation for Windows compatibility.

PREDEF_INIT_FUNC defines TL_EXPORT (as __declspec(dllexport) on Windows) and applies it to get_last_error and init, while PREDEF_HOST_FUNC applies it to the call function in GPU backends. However, TLCPUSourceWrapper.CALL_PREFIX declares call with only extern "C" and no TL_EXPORT annotation. On Windows with MSVC, without this annotation, the symbol won't be exported from the generated DLL, causing symbol resolution failures when attempting to load the CPU-compiled kernel.

Suggested fix
     CALL_PREFIX = textwrap.dedent("""
         `#ifdef` __cplusplus
-        extern "C"
+        extern "C" TL_EXPORT
         `#endif`
         int32_t call({}) {{
           return {};
         }}
     """)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/jit/adapter/wrapper.py` around lines 751 - 758, The CALL_PREFIX in
TLCPUSourceWrapper is missing the TL_EXPORT annotation used by PREDEF_INIT_FUNC
and PREDEF_HOST_FUNC, so update CALL_PREFIX to apply TL_EXPORT to the exported
call symbol (keep extern "C" and the int32_t signature) so the declaration
matches the pattern used for get_last_error/init and GPU call exports; ensure
the TL_EXPORT macro is placed alongside extern "C" (e.g., extern "C" TL_EXPORT
int32_t call(...)) and that the replacement still fills in the function
parameters/return expression as before.
tilelang/jit/adapter/libgen.py (1)

140-149: ⚠️ Potential issue | 🟠 Major

Windows NVCC invocation is missing the MSVC environment — inconsistent with the fix in compile_cuda.

subprocess.run(command, timeout=timeout) inherits the parent process environment. On Windows, NVCC needs to locate cl.exe (the host compiler), which is the reason tilelang/contrib/nvcc.py::compile_cuda was updated to pass env=get_nvcc_subprocess_env(). The same fix is needed here — otherwise JIT library compilation will fail for any user whose Python process wasn't launched from a VS Developer Command Prompt, even when get_msvc_subprocess_env() can successfully bootstrap MSVC.

🛠️ Proposed fix
-from tilelang.contrib.nvcc import (
-    get_nvcc_compiler,
-    get_target_arch,
-    get_target_compute_version,
-)
+from tilelang.contrib.nvcc import (
+    get_nvcc_compiler,
+    get_nvcc_subprocess_env,
+    get_target_arch,
+    get_target_compute_version,
+)
         try:
             if verbose:
                 print(f"compile_lib compilation command: {' '.join(command)}")
-            ret = subprocess.run(command, timeout=timeout)
+            compile_env = get_nvcc_subprocess_env() if is_cuda_target(target) else None
+            ret = subprocess.run(command, timeout=timeout, env=compile_env)
         except Exception as e:
             raise RuntimeError(f"Compile kernel failed because of {e}") from e
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/jit/adapter/libgen.py` around lines 140 - 149, The subprocess.run
call in libgen.compile_lib (where command is executed and assigned to ret) fails
on Windows because it inherits the parent env and NVCC cannot find MSVC; update
that subprocess.run(...) to pass env=get_nvcc_subprocess_env() (the same helper
used in tilelang/contrib/nvcc.py::compile_cuda) when running on Windows so NVCC
can locate cl.exe and other MSVC tools; ensure you import or reference
get_nvcc_subprocess_env() in this module and only apply it for Windows
invocations.
♻️ Duplicate comments (2)
src/transform/layout_inference.cc (1)

755-761: ⚠️ Potential issue | 🟠 Major

Hard ICHECK on Map<Var, Layout> cast will crash on re-entry.

LayoutInferencer::VisitStmt_(BlockNode) (Lines 1229-1230) writes result_.layout_map (a Map<Buffer, Layout>) back into attr::kLayoutMap. If LayoutInference is ever run a second time on the transformed IR (or any other pass reuses the same annotation key with a Buffer-keyed map), Line 760's ICHECK(map_opt.has_value()) << "layout map must be Map<Var, Layout>" will abort instead of gracefully skipping the already-materialized layout map. Prefer an early return when the cast fails so this pass is idempotent.

Suggested fix
     if (op->annotations.count(attr::kLayoutMap)) {
       auto layout_map_ref = op->annotations.Get(attr::kLayoutMap);
-      ICHECK(layout_map_ref.has_value());
+      if (!layout_map_ref.has_value()) {
+        return;
+      }
       auto map_opt = layout_map_ref.value().as<Map<Var, Layout>>();
-      ICHECK(map_opt.has_value()) << "layout map must be Map<Var, Layout>";
-      auto map = map_opt.value();
+      if (!map_opt.has_value()) {
+        // LayoutInferencer emits Map<Buffer, Layout> on output blocks;
+        // ignore already-materialized annotation forms here.
+        return;
+      }
+      auto map = map_opt.value();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/transform/layout_inference.cc` around lines 755 - 761, Replace the hard
ICHECK when casting the annotation value to Map<Var, Layout> with a safe check
and early return so the pass is idempotent: in the block handling
op->annotations.Get(attr::kLayoutMap) (where you currently call map_opt.value()
and ICHECK(map_opt.has_value())), detect when the annotation cannot be cast to
Map<Var, Layout> (e.g., map_opt.has_value() is false) and simply skip/apply
no-op (return/continue) instead of aborting; this ensures a second run (or a
layout_map keyed by Buffer from LayoutInferencer::VisitStmt_(BlockNode) writing
result_.layout_map) won't crash the pass.
src/target/codegen_cuda.cc (1)

3591-3595: ⚠️ Potential issue | 🟠 Major

tl::__tan() still uses CUDAFastMath — precision regression persists after the extraction.

For float32, CUDAFastMath emits __tanf, but this file intentionally defines CUDAFastMathTan (lines 167-185) with an inline comment explaining that __tanf deviates too much from numpy's tan. Moving the handler into HandleLateIntrinsicCall carried over the incorrect functor, so the fp32 tangent path still falls back to the less accurate intrinsic.

Proposed fix
   } else if (op->op.same_as(tl::__tan())) {
-    CUDAFastMath math_func;
+    CUDAFastMathTan math_func;
     std::string func_name = math_func(op->dtype, "tan");
     os << func_name << "(" << PrintExpr(op->args[0]) << ")";
     return true;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/target/codegen_cuda.cc` around lines 3591 - 3595, The tl::__tan() branch
is still constructing CUDAFastMath (which emits __tanf for float32) causing a
precision regression; update the handler in HandleLateIntrinsicCall (the
tl::__tan() case) to use CUDAFastMathTan instead of CUDAFastMath so fp32 paths
call the higher-accuracy implementation declared as CUDAFastMathTan (preserve
the current call pattern that builds func_name and prints the arg via
PrintExpr(op->args[0])). Ensure you replace the CUDAFastMath instantiation with
CUDAFastMathTan and keep the emitted function name logic identical so other
types are unaffected.
🧹 Nitpick comments (9)
src/layout/gemm_layouts.cc (1)

14-20: LGTM — portable unreachable hint.

The macro correctly maps to the canonical builtins per compiler and falls back safely. Ordering _MSC_VER before __clang__ also handles clang-cl correctly (which defines both and supports __assume(0)).

Two optional nits you can ignore:

  • Since this lives in a .cc and is only used locally, you could #undef TILELANG_COMPILER_UNREACHABLE at the bottom of the file to avoid leaking the macro if this header-style block ever gets relocated.
  • On the #else fallback, ((void)0) gives no UB hint, so some compilers may warn about a missing return at the call sites. If that becomes an issue on an exotic toolchain, consider std::abort() (requires <cstdlib>) as a safer fallback.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/layout/gemm_layouts.cc` around lines 14 - 20, Macro
TILELANG_COMPILER_UNREACHABLE is fine but please add a cleanup and an optional
safer fallback: at the end of src/layout/gemm_layouts.cc undefine
TILELANG_COMPILER_UNREACHABLE to avoid leaking the macro, and if you hit
missing-return warnings on exotic toolchains replace the `#else` fallback
((void)0) with a call to std::abort() (add `#include` <cstdlib>), keeping the
existing branches for _MSC_VER and __GNUC__/__clang__ unchanged.
src/runtime/logging.cc (1)

126-128: Duplicate filename=<level> entries silently keep the first value.

std::unordered_map::emplace does not overwrite on existing keys, so specs like TVM_LOG_DEBUG="foo.cc=1,foo.cc=3" silently ignore the 3 and keep 1. This matches the upstream TVM behavior, so it's likely intentional, but if last-writer-wins is preferred here, using operator[] or insert_or_assign would be clearer. Also consider emitting a warning (or at least avoiding the LOG(INFO) for the shadowed entry) to avoid misleading users who see both assignments acknowledged.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/logging.cc` around lines 126 - 128, The current code uses
settings.vlog_level_map_.emplace(name, level_val) which silently keeps the first
value for duplicate keys (e.g., "foo.cc=1,foo.cc=3"); change this to
insert_or_assign(name, level_val) (or settings.vlog_level_map_[name] =
level_val) so the last-writer wins, and add a LOG(WARNING) (or avoid LOG(INFO)
for shadowed entries) when a duplicate key is detected to inform users that an
earlier value was overwritten; reference settings.vlog_level_map_ and the code
block handling TVM_LOG_DEBUG parsing in logging.cc to locate and apply the
change.
src/target/stubs/cuda.h (1)

231-231: Nit: trailing #endif comment understates the condition.

The guard opened at line 159 is #if !defined(_WIN32) || defined(__CYGWIN__), so the closing comment // !_WIN32 is slightly misleading — under Cygwin (where __CYGWIN__ is defined) the block is still compiled regardless of _WIN32. A tiny tweak keeps future readers from assuming the guard is purely "non-Windows".

📝 Proposed comment tweak
-#endif // !_WIN32
+#endif // !_WIN32 || __CYGWIN__
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/target/stubs/cuda.h` at line 231, The closing preprocessor comment
misstates the opening guard: update the trailing comment on the closing '#endif
// !_WIN32' to match the opener '#if !defined(_WIN32) || defined(__CYGWIN__)' so
readers see the full condition (for example: change to '#endif //
!defined(_WIN32) || defined(__CYGWIN__)'), referencing the original '#if
!defined(_WIN32) || defined(__CYGWIN__)' and the existing closing '#endif'.
docs/get_started/Installation.md (1)

77-90: Option A installs the CUDA toolkit twice.

.[nvcc] already resolves to cuda-toolkit[cccl,nvcc,nvrtc]>=13 (see pyproject.toml lines 55-64), so the preceding explicit pip install "cuda-toolkit[cccl,nvcc,nvrtc]>=13" is redundant and just slows first-time installs. Consider dropping it and letting the extra handle it, which also keeps the version constraint in a single place.

♻️ Proposed simplification
 pip install -r requirements-dev.txt
-pip install "cuda-toolkit[cccl,nvcc,nvrtc]>=13"
 pip install .[nvcc] -v
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/get_started/Installation.md` around lines 77 - 90, Remove the redundant
explicit installation of the CUDA toolkit: the step `pip install
"cuda-toolkit[cccl,nvcc,nvrtc]>=13"` is unnecessary because the package extra
`.[nvcc]` already requires `cuda-toolkit[cccl,nvcc,nvrtc]>=13` (see the
dependency block in pyproject.toml). Update the Installation.md Option A
instructions to drop that explicit pip install line and keep only `pip install
.[nvcc] -v` (after installing requirements-dev.txt), ensuring the version
constraint stays centralized in pyproject.toml.
scripts/windows/uv-sync.cmd (1)

24-38: Optional: de-duplicate the two vswhere lookups.

The PF(x86) and PF probes are identical aside from the root env var. Folding them into a small :find_vsdevcmd subroutine (or iterating over both roots) keeps behavior the same while halving the block that has to be kept in sync if the vswhere invocation ever changes (e.g., adding -prerelease, tweaking -requires, etc.). Non-blocking — the current form works.

♻️ Sketch
-if not defined VSDEVCMD (
-  if exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" (
-    for /f "usebackq delims=" %%I in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -find Common7\Tools\VsDevCmd.bat`) do (
-      if not defined VSDEVCMD set "VSDEVCMD=%%~fI"
-    )
-  )
-)
-
-if not defined VSDEVCMD (
-  if exist "%ProgramFiles%\Microsoft Visual Studio\Installer\vswhere.exe" (
-    for /f "usebackq delims=" %%I in (`"%ProgramFiles%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -find Common7\Tools\VsDevCmd.bat`) do (
-      if not defined VSDEVCMD set "VSDEVCMD=%%~fI"
-    )
-  )
-)
+for %%R in ("%ProgramFiles(x86)%" "%ProgramFiles%") do (
+  if not defined VSDEVCMD if exist "%%~R\Microsoft Visual Studio\Installer\vswhere.exe" (
+    for /f "usebackq delims=" %%I in (`"%%~R\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -find Common7\Tools\VsDevCmd.bat`) do (
+      if not defined VSDEVCMD set "VSDEVCMD=%%~fI"
+    )
+  )
+)

Worth a quick sanity-run since combining %%R expansion with nested %%I and %ProgramFiles(x86)% paren-in-quotes is a known batch gotcha — please confirm the refactored form still resolves VsDevCmd on a machine that only has VS in %ProgramFiles% (x64-only install).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/windows/uv-sync.cmd` around lines 24 - 38, Refactor the duplicated
vswhere lookup into a single reusable step: create a small batch subroutine
(e.g., :find_vsdevcmd) or loop over the two roots ("%ProgramFiles(x86)%" and
"%ProgramFiles%") that runs the same vswhere invocation and sets the VSDEVCMD
variable when it finds Common7\Tools\VsDevCmd.bat; ensure you still check if
VSDEVCMD is defined before calling vswhere and preserve the exact vswhere
arguments (-latest -products * -requires
Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -find
Common7\Tools\VsDevCmd.bat). After implementing, sanity-run the script on a
machine with VS installed only in %ProgramFiles% to confirm the subroutine/loop
correctly resolves VsDevCmd and sets VSDEVCMD.
tilelang/libinfo.py (1)

49-56: Simplify the for…else.

The inner body uses return, so the outer loop never breaks; the else clause always executes when nothing was returned, making it equivalent to putting the raise after the loop. The current form is easy to misread.

♻️ Proposed refactor
     for lib_root in TL_LIBS:
         for lib_name in lib_names:
             lib_dll_path = os.path.join(lib_root, lib_name)
             if os.path.exists(lib_dll_path) and os.path.isfile(lib_dll_path):
                 return lib_dll_path
-    else:
-        message = f"Cannot find libraries: {', '.join(lib_names)}\n" + "List of candidates:\n" + "\n".join(TL_LIBS)
-        raise RuntimeError(message)
+    message = f"Cannot find libraries: {', '.join(lib_names)}\n" + "List of candidates:\n" + "\n".join(TL_LIBS)
+    raise RuntimeError(message)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/libinfo.py` around lines 49 - 56, The for...else is misleading
because the inner loop returns on success and the else always runs when nothing
is returned; refactor the search by removing the for...else and placing the
error construction and RuntimeError raise after the nested loops: iterate
TL_LIBS and lib_names, build lib_dll_path and return when os.path.exists and
os.path.isfile succeed (same checks using lib_dll_path), and if no match is
found after both loops construct the same message (using lib_names and TL_LIBS)
and raise RuntimeError; this keeps symbols TL_LIBS, lib_names, lib_dll_path and
the same behavior but eliminates the confusing else clause.
src/target/stubs/dynlib.h (1)

62-95: Optional: replace manual malloc/realloc/free with std::vector<HMODULE>.

The current code is correct but has several free paths that are easy to regress on future edits. A std::vector<HMODULE> with resize() gives you RAII-cleanup and removes the three explicit free(modules) bail-outs.

♻️ Proposed refactor sketch
 inline void *dynlib_find_loaded(const char *symbol_name,
                                 void *exclude = nullptr) {
   HANDLE process = GetCurrentProcess();
-  DWORD buf_bytes = 1024 * sizeof(HMODULE);
-  HMODULE *modules = static_cast<HMODULE *>(malloc(buf_bytes));
-  if (!modules)
-    return nullptr;
+  std::vector<HMODULE> modules(1024);
+  DWORD buf_bytes = static_cast<DWORD>(modules.size() * sizeof(HMODULE));
   DWORD needed = 0;
-  if (!EnumProcessModules(process, modules, buf_bytes, &needed)) {
-    free(modules);
+  if (!EnumProcessModules(process, modules.data(), buf_bytes, &needed))
     return nullptr;
-  }
   if (needed > buf_bytes) {
-    buf_bytes = needed;
-    HMODULE *resized = static_cast<HMODULE *>(realloc(modules, buf_bytes));
-    if (!resized) {
-      free(modules);
-      return nullptr;
-    }
-    modules = resized;
-    if (!EnumProcessModules(process, modules, buf_bytes, &needed)) {
-      free(modules);
+    modules.resize(needed / sizeof(HMODULE));
+    buf_bytes = static_cast<DWORD>(modules.size() * sizeof(HMODULE));
+    if (!EnumProcessModules(process, modules.data(), buf_bytes, &needed))
       return nullptr;
-    }
   }
   DWORD count = needed / sizeof(HMODULE);
   for (DWORD i = 0; i < count; i++) {
     void *sym =
         reinterpret_cast<void *>(GetProcAddress(modules[i], symbol_name));
-    if (sym != nullptr && sym != exclude) {
-      free(modules);
+    if (sym != nullptr && sym != exclude)
       return reinterpret_cast<void *>(modules[i]);
-    }
   }
-  free(modules);
   return nullptr;
 }

(Requires adding #include <vector> at the top of the Windows section.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/target/stubs/dynlib.h` around lines 62 - 95, Replace the manual
malloc/realloc/free buffer handling in the function that enumerates modules
(uses EnumProcessModules, modules, needed, buf_bytes) with a
std::vector<HMODULE>: add `#include` <vector>, create a vector<HMODULE> v;
v.resize(initial_size); call EnumProcessModules(&v[0], v.size() *
sizeof(HMODULE), &needed) and if needed > current bytes resize the vector and
retry; compute count = needed / sizeof(HMODULE) and iterate using v[i] when
calling GetProcAddress(symbol_name) and comparing to exclude, returning the
module handle as before—this removes all explicit free() calls and guarantees
RAII cleanup on every early return.
cmake/pypi-z3/FindZ3.cmake (1)

36-40: Dead if(WIN32) around add_library — both branches are identical.

Both the Windows and non-Windows branches call add_library(z3::libz3 SHARED IMPORTED GLOBAL) with the same arguments, so the condition can be dropped.

🧹 Proposed simplification
-if(WIN32)
-    add_library(z3::libz3 SHARED IMPORTED GLOBAL)
-else()
-    add_library(z3::libz3 SHARED IMPORTED GLOBAL)
-endif()
+add_library(z3::libz3 SHARED IMPORTED GLOBAL)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/pypi-z3/FindZ3.cmake` around lines 36 - 40, The if(WIN32) conditional
around add_library is redundant because both branches call the same command;
remove the conditional block and leave a single add_library(z3::libz3 SHARED
IMPORTED GLOBAL) call in FindZ3.cmake so the z3::libz3 target is defined
unconditionally (referencing the add_library invocation and the z3::libz3
target).
tilelang/contrib/cc.py (1)

53-60: Consider extracting the MSVC cl.exe probe into a helper to avoid duplication.

The 5-line Windows branch is copy-pasted between get_cc() and get_cplus_compiler(). A small private helper (e.g., _probe_msvc_cl()) that returns the resolved path or None would keep both functions in sync if the discovery logic ever evolves.

Also applies to: 84-91

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/contrib/cc.py` around lines 53 - 60, Extract the duplicated Windows
MSVC probe into a single private helper (e.g., _probe_msvc_cl()) and use it from
both get_cc() and get_cplus_compiler(); move the logic that imports
tilelang.contrib.msvc, calls get_msvc_subprocess_env(), computes
get_env_path(compiler_env or {}), and resolves cl via shutil.which("cl.exe",
path=...) into that helper which should return the resolved path or None, then
replace the duplicated blocks in get_cc() and get_cplus_compiler() with calls to
_probe_msvc_cl().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CMakeLists.txt`:
- Around line 13-78: tilelang_generate_windows_import_library currently defaults
_tilelang_machine to "X64" and only checks CMAKE_GENERATOR_PLATFORM for
"Win32"/"ARM64", which misses single-config generators like Ninja; update the
machine-detection logic in tilelang_generate_windows_import_library to also
consult CMAKE_SYSTEM_PROCESSOR and CMAKE_VS_PLATFORM_NAME (and/or
CMAKE_SIZEOF_VOID_P == 4) as fallbacks before defaulting to X64 so
cross-compiles to x86/ARM64 with Ninja are correctly handled; modify the block
that sets _tilelang_machine (and any conditional branches referencing
CMAKE_GENERATOR_PLATFORM) to prefer CMAKE_GENERATOR_PLATFORM, then
CMAKE_VS_PLATFORM_NAME, then CMAKE_SYSTEM_PROCESSOR (normalize values like
"x86"/"AMD64"/"arm64"/"aarch64"), and finally default to X64.

In `@docs/get_started/Installation.md`:
- Around line 122-132: Update the Option C example for WITH_PIP_CUDA_TOOLCHAIN
to include Windows equivalents: add instructions showing how to set the
environment variable from a Developer Command Prompt using the `set` syntax and
from PowerShell using the `$env:` syntax, and clarify typical Windows path
formats like .\.venv and .\.venv\Lib\site-packages; modify the Installation.md
Option C block and the trailing note so readers on Windows have direct, runnable
commands for both cmd.exe and PowerShell in addition to the existing bash
example.

In `@src/op/parallel.cc`:
- Around line 85-92: The annotation type for attr::kReducerInfo is inconsistent:
either make it uniformly Map<Var, ReducerInfo> or keep it as Map<Var,
Map<String,String>>; audit and update all readers/writers accordingly.
Concretely, ensure the writer that sets inside_reducer_range_ (the code that
constructs the annotation in layout_reducer.cc) and places it into
op->annotations uses the same type as readers that call
annotations.Get(attr::kReducerInfo) (e.g., the cast in parallel.cc using
reducer_info_map_, the BlockNode reader in layout_reducer.cc,
verify_parallel_loop.cc, and lower_tile_op.cc). If the intended canonical type
is Map<Var, ReducerInfo>, change the casts and subsequent handling in the
readers to use Map<Var, ReducerInfo> and adapt field access to ReducerInfo;
otherwise revert the writer to emit Map<Var, Map<String,String>> and in readers
that need ReducerInfo perform an explicit field-by-field conversion from
Map<String,String> to ReducerInfo (mirroring the existing BlockNode conversion
pattern).

In `@src/target/stubs/cudart.cc`:
- Around line 107-111: Update the error message emitted in cudart.cc where
fprintf(stderr, "...") and abort() are called: change the wording to mention
Windows DLL names (cudart64_*.dll), pip-installed CUDA wheels, and standalone
CUDA toolkit installs as possible sources of missing symbols rather than only
saying "install PyTorch with CUDA"; locate the fprintf in the cudart
symbols-not-found branch and replace the message with a concise multi-platform
guidance that suggests checking CUDA toolkit installation, PyTorch CUDA builds,
or pip CUDA wheels and verifying DLL/so paths/resolvability before aborting.

In `@tilelang/contrib/msvc.py`:
- Around line 174-179: The MD5 call used to produce the deterministic filename
prefix (the hashlib.md5(...) call that assigns prefix) should be explicitly
marked as non-security-sensitive by passing usedforsecurity=False; update the
call that builds prefix so it uses hashlib.md5(..., usedforsecurity=False). This
affects the prefix generation (variable name prefix) and ensures the intent is
clear to linters and FIPS systems while leaving the rest of the patched_path
creation and file write (patched_path, tmp_dir, patched) unchanged.

---

Outside diff comments:
In `@tilelang/jit/adapter/libgen.py`:
- Around line 140-149: The subprocess.run call in libgen.compile_lib (where
command is executed and assigned to ret) fails on Windows because it inherits
the parent env and NVCC cannot find MSVC; update that subprocess.run(...) to
pass env=get_nvcc_subprocess_env() (the same helper used in
tilelang/contrib/nvcc.py::compile_cuda) when running on Windows so NVCC can
locate cl.exe and other MSVC tools; ensure you import or reference
get_nvcc_subprocess_env() in this module and only apply it for Windows
invocations.

In `@tilelang/jit/adapter/wrapper.py`:
- Around line 751-758: The CALL_PREFIX in TLCPUSourceWrapper is missing the
TL_EXPORT annotation used by PREDEF_INIT_FUNC and PREDEF_HOST_FUNC, so update
CALL_PREFIX to apply TL_EXPORT to the exported call symbol (keep extern "C" and
the int32_t signature) so the declaration matches the pattern used for
get_last_error/init and GPU call exports; ensure the TL_EXPORT macro is placed
alongside extern "C" (e.g., extern "C" TL_EXPORT int32_t call(...)) and that the
replacement still fills in the function parameters/return expression as before.

---

Duplicate comments:
In `@src/target/codegen_cuda.cc`:
- Around line 3591-3595: The tl::__tan() branch is still constructing
CUDAFastMath (which emits __tanf for float32) causing a precision regression;
update the handler in HandleLateIntrinsicCall (the tl::__tan() case) to use
CUDAFastMathTan instead of CUDAFastMath so fp32 paths call the higher-accuracy
implementation declared as CUDAFastMathTan (preserve the current call pattern
that builds func_name and prints the arg via PrintExpr(op->args[0])). Ensure you
replace the CUDAFastMath instantiation with CUDAFastMathTan and keep the emitted
function name logic identical so other types are unaffected.

In `@src/transform/layout_inference.cc`:
- Around line 755-761: Replace the hard ICHECK when casting the annotation value
to Map<Var, Layout> with a safe check and early return so the pass is
idempotent: in the block handling op->annotations.Get(attr::kLayoutMap) (where
you currently call map_opt.value() and ICHECK(map_opt.has_value())), detect when
the annotation cannot be cast to Map<Var, Layout> (e.g., map_opt.has_value() is
false) and simply skip/apply no-op (return/continue) instead of aborting; this
ensures a second run (or a layout_map keyed by Buffer from
LayoutInferencer::VisitStmt_(BlockNode) writing result_.layout_map) won't crash
the pass.

---

Nitpick comments:
In `@cmake/pypi-z3/FindZ3.cmake`:
- Around line 36-40: The if(WIN32) conditional around add_library is redundant
because both branches call the same command; remove the conditional block and
leave a single add_library(z3::libz3 SHARED IMPORTED GLOBAL) call in
FindZ3.cmake so the z3::libz3 target is defined unconditionally (referencing the
add_library invocation and the z3::libz3 target).

In `@docs/get_started/Installation.md`:
- Around line 77-90: Remove the redundant explicit installation of the CUDA
toolkit: the step `pip install "cuda-toolkit[cccl,nvcc,nvrtc]>=13"` is
unnecessary because the package extra `.[nvcc]` already requires
`cuda-toolkit[cccl,nvcc,nvrtc]>=13` (see the dependency block in
pyproject.toml). Update the Installation.md Option A instructions to drop that
explicit pip install line and keep only `pip install .[nvcc] -v` (after
installing requirements-dev.txt), ensuring the version constraint stays
centralized in pyproject.toml.

In `@scripts/windows/uv-sync.cmd`:
- Around line 24-38: Refactor the duplicated vswhere lookup into a single
reusable step: create a small batch subroutine (e.g., :find_vsdevcmd) or loop
over the two roots ("%ProgramFiles(x86)%" and "%ProgramFiles%") that runs the
same vswhere invocation and sets the VSDEVCMD variable when it finds
Common7\Tools\VsDevCmd.bat; ensure you still check if VSDEVCMD is defined before
calling vswhere and preserve the exact vswhere arguments (-latest -products *
-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -find
Common7\Tools\VsDevCmd.bat). After implementing, sanity-run the script on a
machine with VS installed only in %ProgramFiles% to confirm the subroutine/loop
correctly resolves VsDevCmd and sets VSDEVCMD.

In `@src/layout/gemm_layouts.cc`:
- Around line 14-20: Macro TILELANG_COMPILER_UNREACHABLE is fine but please add
a cleanup and an optional safer fallback: at the end of
src/layout/gemm_layouts.cc undefine TILELANG_COMPILER_UNREACHABLE to avoid
leaking the macro, and if you hit missing-return warnings on exotic toolchains
replace the `#else` fallback ((void)0) with a call to std::abort() (add `#include`
<cstdlib>), keeping the existing branches for _MSC_VER and __GNUC__/__clang__
unchanged.

In `@src/runtime/logging.cc`:
- Around line 126-128: The current code uses
settings.vlog_level_map_.emplace(name, level_val) which silently keeps the first
value for duplicate keys (e.g., "foo.cc=1,foo.cc=3"); change this to
insert_or_assign(name, level_val) (or settings.vlog_level_map_[name] =
level_val) so the last-writer wins, and add a LOG(WARNING) (or avoid LOG(INFO)
for shadowed entries) when a duplicate key is detected to inform users that an
earlier value was overwritten; reference settings.vlog_level_map_ and the code
block handling TVM_LOG_DEBUG parsing in logging.cc to locate and apply the
change.

In `@src/target/stubs/cuda.h`:
- Line 231: The closing preprocessor comment misstates the opening guard: update
the trailing comment on the closing '#endif // !_WIN32' to match the opener '#if
!defined(_WIN32) || defined(__CYGWIN__)' so readers see the full condition (for
example: change to '#endif // !defined(_WIN32) || defined(__CYGWIN__)'),
referencing the original '#if !defined(_WIN32) || defined(__CYGWIN__)' and the
existing closing '#endif'.

In `@src/target/stubs/dynlib.h`:
- Around line 62-95: Replace the manual malloc/realloc/free buffer handling in
the function that enumerates modules (uses EnumProcessModules, modules, needed,
buf_bytes) with a std::vector<HMODULE>: add `#include` <vector>, create a
vector<HMODULE> v; v.resize(initial_size); call EnumProcessModules(&v[0],
v.size() * sizeof(HMODULE), &needed) and if needed > current bytes resize the
vector and retry; compute count = needed / sizeof(HMODULE) and iterate using
v[i] when calling GetProcAddress(symbol_name) and comparing to exclude,
returning the module handle as before—this removes all explicit free() calls and
guarantees RAII cleanup on every early return.

In `@tilelang/contrib/cc.py`:
- Around line 53-60: Extract the duplicated Windows MSVC probe into a single
private helper (e.g., _probe_msvc_cl()) and use it from both get_cc() and
get_cplus_compiler(); move the logic that imports tilelang.contrib.msvc, calls
get_msvc_subprocess_env(), computes get_env_path(compiler_env or {}), and
resolves cl via shutil.which("cl.exe", path=...) into that helper which should
return the resolved path or None, then replace the duplicated blocks in get_cc()
and get_cplus_compiler() with calls to _probe_msvc_cl().

In `@tilelang/libinfo.py`:
- Around line 49-56: The for...else is misleading because the inner loop returns
on success and the else always runs when nothing is returned; refactor the
search by removing the for...else and placing the error construction and
RuntimeError raise after the nested loops: iterate TL_LIBS and lib_names, build
lib_dll_path and return when os.path.exists and os.path.isfile succeed (same
checks using lib_dll_path), and if no match is found after both loops construct
the same message (using lib_names and TL_LIBS) and raise RuntimeError; this
keeps symbols TL_LIBS, lib_names, lib_dll_path and the same behavior but
eliminates the confusing else clause.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1871ce26-d4a5-43bd-be60-1a9c9b709986

📥 Commits

Reviewing files that changed from the base of the PR and between e876998 and c2a85be.

📒 Files selected for processing (35)
  • .gitattributes
  • .gitignore
  • CMakeLists.txt
  • cmake/FindPipCUDAToolkit.cmake
  • cmake/find_pip_cuda.py
  • cmake/generate_windows_import_lib.py
  • cmake/pypi-z3/FindZ3.cmake
  • docs/get_started/Installation.md
  • pyproject.toml
  • scripts/windows/uv-sync.cmd
  • src/layout/gemm_layouts.cc
  • src/op/operator.h
  • src/op/parallel.cc
  • src/runtime/logging.cc
  • src/target/codegen_cuda.cc
  • src/target/codegen_cuda.h
  • src/target/stubs/cuda.cc
  • src/target/stubs/cuda.h
  • src/target/stubs/cudart.cc
  • src/target/stubs/dynlib.h
  • src/target/stubs/nvrtc.cc
  • src/transform/common/pipeline_utils.h
  • src/transform/layout_inference.cc
  • src/transform/layout_reducer.cc
  • tilelang/__init__.py
  • tilelang/cache/kernel_cache.py
  • tilelang/carver/arch/driver/cuda_driver.py
  • tilelang/contrib/cc.py
  • tilelang/contrib/msvc.py
  • tilelang/contrib/nvcc.py
  • tilelang/env.py
  • tilelang/jit/adapter/libgen.py
  • tilelang/jit/adapter/tvm_ffi.py
  • tilelang/jit/adapter/wrapper.py
  • tilelang/libinfo.py
✅ Files skipped from review due to trivial changes (3)
  • .gitignore
  • .gitattributes
  • cmake/find_pip_cuda.py
🚧 Files skipped from review as they are similar to previous changes (7)
  • tilelang/env.py
  • src/transform/layout_reducer.cc
  • tilelang/cache/kernel_cache.py
  • pyproject.toml
  • src/target/stubs/nvrtc.cc
  • src/target/stubs/cuda.cc
  • cmake/FindPipCUDAToolkit.cmake

Comment thread CMakeLists.txt
Comment thread docs/get_started/Installation.md Outdated
Comment thread src/op/parallel.cc
Comment thread src/backend/cuda/codegen/stubs/cudart.cc
Comment thread tilelang/contrib/msvc.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tilelang/jit/adapter/libgen.py (1)

146-154: ⚠️ Potential issue | 🟠 Major

Windows NVCC invocation is missing the MSVC host environment.

subprocess.run(command, timeout=timeout) does not pass env=, so on Windows NVCC inherits only the parent process environment and will not see the MSVC toolchain (PATH, INCLUDE, LIB, WindowsSdkDir, …) discovered by get_nvcc_subprocess_env()/get_msvc_subprocess_env(). Unless the Python process was itself launched from a Visual Studio developer prompt, NVCC will fail to locate cl.exe or the Windows SDK headers and the whole Windows JIT path breaks.

Mirror the treatment in tilelang/contrib/nvcc.py::compile_cuda (which now threads env=compiler_env through subprocess.Popen).

🛠️ Proposed fix
+        from tilelang.contrib.nvcc import get_nvcc_subprocess_env
+        compiler_env = get_nvcc_subprocess_env() if is_cuda_target(target) else None
         try:
             if verbose:
                 print(f"compile_lib compilation command: {' '.join(command)}")
-            ret = subprocess.run(command, timeout=timeout)
+            ret = subprocess.run(command, timeout=timeout, env=compiler_env)
         except Exception as e:
             raise RuntimeError(f"Compile kernel failed because of {e}") from e

(Adjust imports so the helper is only imported for the CUDA branch.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/jit/adapter/libgen.py` around lines 146 - 154, The subprocess.run
call that invokes NVCC must be passed the proper MSVC/NVCC environment so cl.exe
and Windows SDK headers are visible; obtain the environment via the existing
helpers (get_nvcc_subprocess_env()/get_msvc_subprocess_env()) and pass it as
env=compiler_env to subprocess.run(command, timeout=timeout, env=compiler_env)
(mirror tilelang/contrib/nvcc.py::compile_cuda). Only import those helpers for
the CUDA branch if needed and ensure the same env is used for the return-code
check (ret.returncode) and error messages.
♻️ Duplicate comments (1)
src/target/codegen_cuda.cc (1)

3591-3595: ⚠️ Potential issue | 🟠 Major

Use CUDAFastMathTan for tl::__tan().

The tan branch still routes through CUDAFastMath, which emits __tanf for float32. The CUDAFastMathTan specialization exists precisely because __tanf was found too inaccurate vs. tanf, so this keeps the prior precision regression in place after the extraction.

Proposed fix
   } else if (op->op.same_as(tl::__tan())) {
-    CUDAFastMath math_func;
+    CUDAFastMathTan math_func;
     std::string func_name = math_func(op->dtype, "tan");
     os << func_name << "(" << PrintExpr(op->args[0]) << ")";
     return true;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/target/codegen_cuda.cc` around lines 3591 - 3595, The tan operator branch
currently uses CUDAFastMath (producing __tanf for float32) which is too
imprecise; change the branch handling tl::__tan() to use the CUDAFastMathTan
specialization instead of CUDAFastMath so the emitted function respects the
higher-precision tan implementation; update the code that builds func_name (when
op->op.same_as(tl::__tan())) to call CUDAFastMathTan(op->dtype, "tan") rather
than CUDAFastMath, keeping the existing usage of PrintExpr(op->args[0]) and
returning true.
🧹 Nitpick comments (12)
src/layout/gemm_layouts.cc (1)

14-20: LGTM on the compiler-dispatch macro.

The MSVC/GCC/Clang/fallback arms are correct, and the surrounding call sites (Lines 870, 899) only reach the macro after an ICHECK(0) << ... that is guaranteed to throw, so the no-op fallback on unknown compilers is safe w.r.t. correctness. Minor optional nit: if this macro starts being needed elsewhere in the codebase, consider hoisting it into a shared header (e.g., a tl_compiler.h) so the definition isn't duplicated per TU.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/layout/gemm_layouts.cc` around lines 14 - 20, Macro
TILELANG_COMPILER_UNREACHABLE is implemented correctly for MSVC/GCC/Clang with a
safe no-op fallback, but consider hoisting it to a shared header to avoid
duplication; move the TILELANG_COMPILER_UNREACHABLE definition out of
src/layout/gemm_layouts.cc into a common header (e.g., tl_compiler.h) and
replace occurrences in this TU and any others with an `#include` of that header so
callers like the sites that follow ICHECK(0) can reuse the same, single
definition.
tilelang/contrib/msvc.py (1)

152-161: -std=... is silently dropped with no MSVC equivalent emitted.

Callers (e.g., TVM/TileLang JIT FFI glue) commonly pass -std=c++17. Dropping it without appending /std:c++17 leaves cl.exe at its default (C++14 pre-VS2022 defaults), which can cause generated FFI sources that rely on C++17 features (structured bindings, if constexpr, std::string_view) to fail to compile on Windows. Similarly, -g → /Z7 or /Zi should be considered if debug symbols matter for kernel cache debugging.

♻️ Proposed mapping
 def _normalize_option(option: str) -> str | None:
     if option.startswith("-I"):
         return "/I" + option[2:]
     if option.startswith("-D"):
         return "/D" + option[2:]
-    if option in ("-g", "-fPIC"):
-        return None
-    if option.startswith("-std="):
-        return None
+    if option == "-fPIC":
+        return None
+    if option == "-g":
+        return "/Z7"
+    if option.startswith("-std="):
+        return "/std:" + option[len("-std="):]
     return option
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/contrib/msvc.py` around lines 152 - 161, The _normalize_option
function currently drops -std=... and -g options silently; update
_normalize_option to translate -std=<ver> into MSVC's /std:<ver> (e.g.,
-std=c++17 -> /std:c++17) and map -g to an appropriate MSVC debug flag (e.g.,
/Zi or /Z7) instead of returning None; locate and edit the _normalize_option
function in msvc.py and ensure it parses the suffix after "-std=" and returns
"/std:" + suffix, and replace the -g branch to return "/Zi" (or "/Z7" if you
prefer full debug info) so callers like the TVM/TileLang JIT FFI glue that pass
"-std=c++17" get equivalent MSVC flags emitted.
tilelang/jit/adapter/libgen.py (1)

63-66: Path extension replacement is fragile on Windows.

src.name.replace(".cu", _lib_ext) replaces the first occurrence of ".cu" anywhere in the path, not just the extension. If the tempfile lands under a directory that happens to contain .cu (e.g., a user project path, a cached kernel directory named .cuda_cache, etc.), the output libpath will be mangled. This pre-existed but is worth tightening alongside the new Windows extension logic.

♻️ Proposed fix
-            _lib_ext = ".dll" if sys.platform == "win32" else ".so"
-            src = tempfile.NamedTemporaryFile(mode="w", suffix=".cu", delete=False)  # noqa: SIM115
-            target_arch = get_target_arch(get_target_compute_version(target))
-            libpath = src.name.replace(".cu", _lib_ext)
+            _lib_ext = ".dll" if sys.platform == "win32" else ".so"
+            src = tempfile.NamedTemporaryFile(mode="w", suffix=".cu", delete=False)  # noqa: SIM115
+            target_arch = get_target_arch(get_target_compute_version(target))
+            libpath = os.path.splitext(src.name)[0] + _lib_ext

The same concern applies to the .cpp → .so paths at lines 106 and 124.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/jit/adapter/libgen.py` around lines 63 - 66, The code currently
builds library paths by calling src.name.replace(".cu", _lib_ext) which can
accidentally replace ".cu" anywhere in the temp path; update the logic to
replace only the file extension using os.path.splitext on src.name (e.g., use
os.path.splitext(src.name)[0] + _lib_ext) for the .cu→.dll/.so case (around the
src = tempfile.NamedTemporaryFile(...) / libpath = ... block) and apply the same
change to the other occurrences where .cpp is converted to .so (the .cpp → .so
conversions later in the file) so extension replacement is robust on Windows and
other platforms.
src/target/stubs/dynlib.h (1)

59-95: Replace manual malloc/realloc with std::vector<HMODULE>.

This is a C++ header (uses namespace, reinterpret_cast, thread_local), so prefer RAII over paired malloc/free across multiple early-return branches — it removes the risk of leaks if the code is extended and makes the grow-and-retry logic more idiomatic.

♻️ Proposed refactor
 inline void *dynlib_find_loaded(const char *symbol_name,
                                 void *exclude = nullptr) {
   HANDLE process = GetCurrentProcess();
-  DWORD buf_bytes = 1024 * sizeof(HMODULE);
-  HMODULE *modules = static_cast<HMODULE *>(malloc(buf_bytes));
-  if (!modules)
-    return nullptr;
+  std::vector<HMODULE> modules(1024);
+  DWORD buf_bytes = static_cast<DWORD>(modules.size() * sizeof(HMODULE));
   DWORD needed = 0;
-  if (!EnumProcessModules(process, modules, buf_bytes, &needed)) {
-    free(modules);
+  if (!EnumProcessModules(process, modules.data(), buf_bytes, &needed)) {
     return nullptr;
   }
   if (needed > buf_bytes) {
-    buf_bytes = needed;
-    HMODULE *resized = static_cast<HMODULE *>(realloc(modules, buf_bytes));
-    if (!resized) {
-      free(modules);
-      return nullptr;
-    }
-    modules = resized;
-    if (!EnumProcessModules(process, modules, buf_bytes, &needed)) {
-      free(modules);
+    modules.resize(needed / sizeof(HMODULE));
+    buf_bytes = static_cast<DWORD>(modules.size() * sizeof(HMODULE));
+    if (!EnumProcessModules(process, modules.data(), buf_bytes, &needed)) {
       return nullptr;
     }
   }
   DWORD count = needed / sizeof(HMODULE);
   for (DWORD i = 0; i < count; i++) {
     void *sym =
         reinterpret_cast<void *>(GetProcAddress(modules[i], symbol_name));
     if (sym != nullptr && sym != exclude) {
-      free(modules);
       return reinterpret_cast<void *>(modules[i]);
     }
   }
-  free(modules);
   return nullptr;
 }

Remember to #include <vector> in the Windows branch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/target/stubs/dynlib.h` around lines 59 - 95, The function
dynlib_find_loaded uses manual malloc/realloc/free and early returns; replace
that with a std::vector<HMODULE> to own the buffer and avoid leaks: include
<vector>, create a vector<HMODULE> modules; set an initial size using buf_bytes
/ sizeof(HMODULE) (or reserve/resize), pass modules.data() to
EnumProcessModules, check needed and if greater than current capacity resize the
vector (resize to needed/sizeof(HMODULE)) and call EnumProcessModules again,
then iterate modules[0..count) and call GetProcAddress as before; remove all
malloc/realloc/free and ensure returned module pointer logic and exclude
comparison remain unchanged.
tilelang/carver/arch/driver/cuda_driver.py (1)

55-65: Minor: cudart64_11.dll is not a real CUDA runtime library name.

CUDA 11.x ships cudart64_110.dll (which is already in the list); there is no cudart64_11.dll. The extra "11" entry is harmless but dead. Consider also covering future versions or deriving the suffix from CUDA_PATH / discovered nvcc to be more robust.

♻️ Proposed tweak
-    if sys.platform == "win32":
-        for ver in ("13", "12", "110", "11"):
-            try:
-                return ctypes.windll.LoadLibrary(f"cudart64_{ver}.dll")
-            except OSError:
-                continue
-        raise OSError("Cannot find cudart64_*.dll")
+    if sys.platform == "win32":
+        for ver in ("13", "12", "110"):
+            try:
+                return ctypes.windll.LoadLibrary(f"cudart64_{ver}.dll")
+            except OSError:
+                continue
+        raise OSError("Cannot find cudart64_*.dll")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/carver/arch/driver/cuda_driver.py` around lines 55 - 65, The Windows
DLL search in _load_cudart currently includes a dead "11" suffix; remove the
"11" entry from the for-loop and replace the hardcoded list with a more robust
strategy: either (a) enumerate matching files using a glob like "cudart64_*.dll"
and pick the highest/first match, or (b) derive the proper suffix from
environment info (CUDA_PATH or nvcc --version) and try that suffix first,
falling back to a glob scan; ensure the function still falls back to raising
OSError if nothing is found.
cmake/FindPipCUDAToolkit.cmake (1)

243-258: Duplicate PATH/PATHEXT/SDK-bin prepends inside _tilelang_activate_msvc_env.

Lines 243–250 re-prepend _tilelang_system_path_candidates to ENV{PATH} despite lines 68–75 already doing this. Similarly, lines 251–255 re-apply the same PATHEXT logic as lines 77–81, and line 257 re-prepends _tilelang_sdk_bin after line 170 already did. This causes PATH to accumulate duplicate SDK/system entries on every configure and effectively pushes MSVC include paths away from the front as the function runs.

Consider removing the duplicated second block (lines 243–258) or, if it was intended to re-assert precedence after the INCLUDE/env block mutated PATH, de-duplicate the resulting PATH list explicitly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/FindPipCUDAToolkit.cmake` around lines 243 - 258, The function
_tilelang_activate_msvc_env is prepending _tilelang_system_path_candidates,
PATHEXT logic, and _tilelang_sdk_bin twice which causes PATH/PATHEXT
duplication; remove the second block (the repeated handling of
_tilelang_system_path_candidates, PATHEXT, and _tilelang_sdk_bin) or, if you
intended to ensure precedence after the INCLUDE/env modifications, replace the
second block with a de-dup step that normalizes ENV{PATH} and ENV{PATHEXT}
(split on ';', remove duplicate entries while preserving order, then re-join) so
that ENV{PATH} and ENV{PATHEXT} contain each entry only once; update code
referencing _tilelang_system_path_candidates, _tilelang_default_pathext, and
_tilelang_sdk_bin accordingly.
tilelang/env.py (1)

47-53: Standardize sys.platform comparison for consistency.

The code correctly prepends TL_LIBS to PATH for child subprocesses on Windows. However, line 47 uses sys.platform.startswith("win32") while line 112 in the same file uses sys.platform == "win32". Since sys.platform on Windows is exactly "win32", both are functionally equivalent, but standardizing to == "win32" (or startswith()) improves readability. Note: os.add_dll_directory() is already invoked separately in tilelang/__init__.py (lines 202–203) via libinfo.get_dll_directories() for Python 3.8+ DLL resolution, so no additional changes needed there.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/env.py` around lines 47 - 53, Change the Windows platform check to
use equality for consistency: replace the condition using
sys.platform.startswith("win32") with sys.platform == "win32" in the block that
builds path_entries/normalized_entries/prepend_entries and updates
os.environ["PATH"] so it matches the other usage in this file (and continues to
prepend TL_LIBS for subprocesses).
tilelang/__init__.py (1)

115-146: Optional: log the swallowed tvm_ffi import failure at debug level.

_prepend_windows_runtime_paths is best-effort, which is fine, but silently swallowing the tvm_ffi import/lookup failure (lines 125-126) makes "why isn't libtvm_ffi on PATH?" hard to diagnose on Windows. A single logger.debug(...) call would give users something to grep without changing behavior. Static analysis flags S110/BLE001 here for the same reason.

🛠️ Suggested change
     try:
         from tvm_ffi import libinfo as tvm_ffi_libinfo

         runtime_paths.append(os.path.dirname(tvm_ffi_libinfo.find_libtvm_ffi()))
-    except Exception:
-        pass
+    except Exception as exc:  # noqa: BLE001 - best-effort PATH setup at import time
+        logger.debug("tvm_ffi libinfo discovery failed: %r", exc)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/__init__.py` around lines 115 - 146, In
_prepend_windows_runtime_paths(), catch the broad Exception from the tvm_ffi
import/lookup and add a logger.debug(...) call that includes the exception
details (e.g., referencing tvm_ffi_libinfo.find_libtvm_ffi and the caught
exception) so the failure is recorded at debug level without changing behavior;
keep the existing pass semantics but emit a single debug log entry when the
import/lookup fails to aid diagnostics.
tilelang/libinfo.py (1)

8-22: Optional: de-duplicate the Windows CUDA path list with tilelang/__init__.py.

The CUDA subdirectory enumeration (bin, bin/x86_64, lib/x64, nvvm/bin) is copy-pasted into __init__.py lines 182-188. As the list grows, keeping them in lockstep is easy to forget. Consider having __init__.py consume get_dll_directories() (or a new helper that returns just the CUDA dirs) as the single source of truth.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/libinfo.py` around lines 8 - 22, Extract the CUDA subdirectory list
into a single helper (e.g., new function get_cuda_dll_subdirs or extend
get_dll_directories to expose the CUDA-only list) and have both
tilelang/libinfo.py (get_dll_directories) and tilelang/__init__.py call that
helper instead of duplicating the literal list; modify get_dll_directories to
call the helper when sys.platform startswith "win32" and update __init__.py to
import and reuse that helper so the CUDA paths are defined in one place
(reference functions: get_dll_directories and the new helper name you add).
src/target/stubs/nvrtc.cc (1)

47-71: Consider mirroring the explanatory comment from cudart.cc here.

cudart.cc (lines 80-81) documents why Windows prefers a fresh dynlib_open over dynlib_find_loaded first (some already-loaded modules re-export a subset of cudart symbols). The same inverted ordering appears here but without any comment, which will make the divergence between the Windows and POSIX branches look accidental to future readers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/target/stubs/nvrtc.cc` around lines 47 - 71, The Windows branch in
TryLoadLibNvrtc uses dynlib_open before dynlib_find_loaded (iterating
kLibNvrtcPaths) but lacks the explanatory comment present in cudart.cc; update
nvrtc.cc by adding a brief comment above the Windows-first block (near
TryLoadLibNvrtc, kLibNvrtcPaths, dynlib_open, dynlib_find_loaded, nvrtcVersion)
mirroring cudart.cc's note: explain that on Windows we prefer a fresh
dynlib_open because some already-loaded modules may re-export a subset of NVRTC
symbols, which would make dynlib_find_loaded return an incomplete handle, so we
attempt explicit opens first.
tilelang/cache/kernel_cache.py (2)

91-111: Stamp is correct but order-sensitive; worth a brief note.

combined_hash mixes in name.encode() and file bytes in the iteration order of lib_dirs × lib_names. Two installs that share identical library contents but differ in TL_LIBS directory ordering will produce different stamps (and therefore different cache namespaces). That's safe (no false hits), just slightly wasteful. Since TL_LIBS is constructed from tilelang.env, this is usually stable, but sorting lib_dirs/lib_names before hashing would make the stamp fully deterministic across arbitrary TL_LIBS permutations. Optional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/cache/kernel_cache.py` around lines 91 - 111, The stamp generation
is order-sensitive because combined_hash mixes name.encode() and file bytes in
the nested loop over lib_dirs and lib_names; make it deterministic by iterating
over sorted(lib_dirs) and sorted(lib_names) (instead of their original order)
when computing combined_hash and appended to found, so the resulting stamp from
combined_hash and '+'.join(found) is stable across TL_LIBS permutations; update
the loops in kernel_cache.py where combined_hash, lib_dirs, lib_names, and found
are used to use sorted(...) accordingly.

413-417: Temp path still uses .so suffix on Windows.

_safe_write_executable writes to {pid}_{uuid}.so before os.replace-ing into the final kernel_lib.so slot. With MSVC fcompile now wired in on Windows, export_library invokes msvc_create_shared, and link.exe also emits a sidecar import lib named after the output base (<...>.lib) next to it in TILELANG_TMP_DIR. The final atomic rename won't clean that sidecar up, so you can accumulate orphaned .lib/.exp files in the tmp dir over time. Consider using a platform-appropriate suffix on Windows (and/or explicitly cleaning up sidecars), e.g.:

♻️ Proposed adjustment
     `@classmethod`
     def _safe_write_executable(cls, executable: Executable, path: str):
-        temp_path = os.path.join(env.TILELANG_TMP_DIR, f"{os.getpid()}_{uuid.uuid4()}.so")
-        executable.export_library(temp_path, **cls._get_compile_args())
-        os.replace(temp_path, path)
+        suffix = ".dll" if sys.platform == "win32" else ".so"
+        temp_path = os.path.join(env.TILELANG_TMP_DIR, f"{os.getpid()}_{uuid.uuid4()}{suffix}")
+        try:
+            executable.export_library(temp_path, **cls._get_compile_args())
+            os.replace(temp_path, path)
+        finally:
+            if sys.platform == "win32":
+                base, _ = os.path.splitext(temp_path)
+                for ext in (".lib", ".exp"):
+                    try:
+                        os.remove(base + ext)
+                    except OSError:
+                        pass
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/cache/kernel_cache.py` around lines 413 - 417,
_safe_write_executable currently always writes a temp file with a .so suffix
which on Windows causes link.exe to emit orphaned .lib/.exp sidecars; change the
temp filename generation in _safe_write_executable to use a platform-appropriate
suffix (e.g. '.dll' on Windows, '.so' on Unix) and after calling
Executable.export_library(temp_path, **cls._get_compile_args()) scan the same
temp directory for sidecars with the same stem (e.g. f"{stem}.lib",
f"{stem}.exp") and remove them (or move them alongside the temp file) before
calling os.replace(temp_path, path) so no orphaned sidecars remain in
env.TILELANG_TMP_DIR. Ensure you still use uuid/os.getpid() to make the temp
name unique and reference env.TILELANG_TMP_DIR, _safe_write_executable,
Executable.export_library and os.replace in your change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmake/generate_windows_import_lib.py`:
- Around line 10-58: The EXPORT_RE currently captures the last token, which
loses the real export name for forwarded or data exports; update the regex
EXPORT_RE to capture the exported symbol name as a separate group and an
optional trailing forwarded/data suffix (e.g.
r"^\s+\d+\s+[0-9A-Fa-f]+\s+[0-9A-Fa-f]+\s+(\S+?)(?:\s*\((forwarded to
.*)\))?(?:,DATA)?\s*$"), then in main where match is used extract the real
symbol from match.group(1) and detect if the original line included ",DATA" or a
forwarded suffix (via match.groups or by inspecting the original line) so you
can (a) skip or correctly handle forwarded exports (emit the original name, not
the forwarded target) and (b) append " DATA" to that export when writing the
.def; update the exports list construction and the def_path.write_text call to
include the " DATA" marker for data exports or otherwise document/raise when
encountering forwarded/data exports if you choose not to support them.

In `@src/target/stubs/nvrtc.cc`:
- Around line 73-76: The error message in nvrtc.cc currently tells users to
"Make sure PyTorch with CUDA is installed" which is misleading for Windows/pip
or standalone CUDA NVRTC installs; update the fprintf in the nvrtc
symbols-not-found branch (the fprintf(stderr, ...) followed by abort()) to use
broader wording similar to cudart.cc—e.g., mention that CUDA NVRTC symbols were
not found and suggest installing a CUDA-enabled PyTorch or a
system/properly-installed CUDA toolkit (or the nvidia-cuda-nvrtc wheel) and/or
verifying CUDA installation and PATH/LD_LIBRARY_PATH; keep the same stderr +
abort() behavior but replace the hardcoded PyTorch-only guidance with the
inclusive message.

In `@src/transform/layout_reducer.cc`:
- Around line 131-139: The code currently ICHECKs that
p_result->annotations.Get(attr::kLayoutMap) can be cast to Map<Var, Layout>,
which will abort when the stored map uses non-Var keys (e.g., Map<Buffer,
Layout> written by LayoutInferencer::VisitStmt_(BlockNode)); change the logic in
ReducerLayoutAnnotator so after obtaining layout_map_ref and maybe_layout_map
you do not ICHECK on maybe_layout_map.has_value(), but instead treat a failed
cast as "nothing to merge" and skip/early-return: if
maybe_layout_map.has_value() assign layout_map = maybe_layout_map.value() else
leave layout_map empty and continue; keep the presence check of attr::kLayoutMap
but avoid aborting on wrong-keyed maps to safely handle Map<Buffer, Layout>.

In `@tilelang/contrib/nvcc.py`:
- Around line 104-105: The added Windows-only compiler option uses the
MSVC-specific flag "/Zc:preprocessor" which requires MSVC 19.25+; update
tilelang/contrib/nvcc.py so you only append ["-Xcompiler", "/Zc:preprocessor"]
to cmd when the host toolchain supports it. Concretely: detect Windows (os.name
== "nt") as before, then probe cl.exe version (e.g., run cl.exe to read its
reported version string or use an environment/toolchain API) and only add the
flag when the parsed major/minor version is >= 19.25 (or else skip the flag);
alternatively add a clear comment and a fallback path that documents the minimum
MSVC requirement if you prefer not to probe. Ensure you reference and modify the
code that builds cmd so the unsafe flag is conditionally applied.

---

Outside diff comments:
In `@tilelang/jit/adapter/libgen.py`:
- Around line 146-154: The subprocess.run call that invokes NVCC must be passed
the proper MSVC/NVCC environment so cl.exe and Windows SDK headers are visible;
obtain the environment via the existing helpers
(get_nvcc_subprocess_env()/get_msvc_subprocess_env()) and pass it as
env=compiler_env to subprocess.run(command, timeout=timeout, env=compiler_env)
(mirror tilelang/contrib/nvcc.py::compile_cuda). Only import those helpers for
the CUDA branch if needed and ensure the same env is used for the return-code
check (ret.returncode) and error messages.

---

Duplicate comments:
In `@src/target/codegen_cuda.cc`:
- Around line 3591-3595: The tan operator branch currently uses CUDAFastMath
(producing __tanf for float32) which is too imprecise; change the branch
handling tl::__tan() to use the CUDAFastMathTan specialization instead of
CUDAFastMath so the emitted function respects the higher-precision tan
implementation; update the code that builds func_name (when
op->op.same_as(tl::__tan())) to call CUDAFastMathTan(op->dtype, "tan") rather
than CUDAFastMath, keeping the existing usage of PrintExpr(op->args[0]) and
returning true.

---

Nitpick comments:
In `@cmake/FindPipCUDAToolkit.cmake`:
- Around line 243-258: The function _tilelang_activate_msvc_env is prepending
_tilelang_system_path_candidates, PATHEXT logic, and _tilelang_sdk_bin twice
which causes PATH/PATHEXT duplication; remove the second block (the repeated
handling of _tilelang_system_path_candidates, PATHEXT, and _tilelang_sdk_bin)
or, if you intended to ensure precedence after the INCLUDE/env modifications,
replace the second block with a de-dup step that normalizes ENV{PATH} and
ENV{PATHEXT} (split on ';', remove duplicate entries while preserving order,
then re-join) so that ENV{PATH} and ENV{PATHEXT} contain each entry only once;
update code referencing _tilelang_system_path_candidates,
_tilelang_default_pathext, and _tilelang_sdk_bin accordingly.

In `@src/layout/gemm_layouts.cc`:
- Around line 14-20: Macro TILELANG_COMPILER_UNREACHABLE is implemented
correctly for MSVC/GCC/Clang with a safe no-op fallback, but consider hoisting
it to a shared header to avoid duplication; move the
TILELANG_COMPILER_UNREACHABLE definition out of src/layout/gemm_layouts.cc into
a common header (e.g., tl_compiler.h) and replace occurrences in this TU and any
others with an `#include` of that header so callers like the sites that follow
ICHECK(0) can reuse the same, single definition.

In `@src/target/stubs/dynlib.h`:
- Around line 59-95: The function dynlib_find_loaded uses manual
malloc/realloc/free and early returns; replace that with a std::vector<HMODULE>
to own the buffer and avoid leaks: include <vector>, create a vector<HMODULE>
modules; set an initial size using buf_bytes / sizeof(HMODULE) (or
reserve/resize), pass modules.data() to EnumProcessModules, check needed and if
greater than current capacity resize the vector (resize to
needed/sizeof(HMODULE)) and call EnumProcessModules again, then iterate
modules[0..count) and call GetProcAddress as before; remove all
malloc/realloc/free and ensure returned module pointer logic and exclude
comparison remain unchanged.

In `@src/target/stubs/nvrtc.cc`:
- Around line 47-71: The Windows branch in TryLoadLibNvrtc uses dynlib_open
before dynlib_find_loaded (iterating kLibNvrtcPaths) but lacks the explanatory
comment present in cudart.cc; update nvrtc.cc by adding a brief comment above
the Windows-first block (near TryLoadLibNvrtc, kLibNvrtcPaths, dynlib_open,
dynlib_find_loaded, nvrtcVersion) mirroring cudart.cc's note: explain that on
Windows we prefer a fresh dynlib_open because some already-loaded modules may
re-export a subset of NVRTC symbols, which would make dynlib_find_loaded return
an incomplete handle, so we attempt explicit opens first.

In `@tilelang/__init__.py`:
- Around line 115-146: In _prepend_windows_runtime_paths(), catch the broad
Exception from the tvm_ffi import/lookup and add a logger.debug(...) call that
includes the exception details (e.g., referencing
tvm_ffi_libinfo.find_libtvm_ffi and the caught exception) so the failure is
recorded at debug level without changing behavior; keep the existing pass
semantics but emit a single debug log entry when the import/lookup fails to aid
diagnostics.

In `@tilelang/cache/kernel_cache.py`:
- Around line 91-111: The stamp generation is order-sensitive because
combined_hash mixes name.encode() and file bytes in the nested loop over
lib_dirs and lib_names; make it deterministic by iterating over sorted(lib_dirs)
and sorted(lib_names) (instead of their original order) when computing
combined_hash and appended to found, so the resulting stamp from combined_hash
and '+'.join(found) is stable across TL_LIBS permutations; update the loops in
kernel_cache.py where combined_hash, lib_dirs, lib_names, and found are used to
use sorted(...) accordingly.
- Around line 413-417: _safe_write_executable currently always writes a temp
file with a .so suffix which on Windows causes link.exe to emit orphaned
.lib/.exp sidecars; change the temp filename generation in
_safe_write_executable to use a platform-appropriate suffix (e.g. '.dll' on
Windows, '.so' on Unix) and after calling Executable.export_library(temp_path,
**cls._get_compile_args()) scan the same temp directory for sidecars with the
same stem (e.g. f"{stem}.lib", f"{stem}.exp") and remove them (or move them
alongside the temp file) before calling os.replace(temp_path, path) so no
orphaned sidecars remain in env.TILELANG_TMP_DIR. Ensure you still use
uuid/os.getpid() to make the temp name unique and reference
env.TILELANG_TMP_DIR, _safe_write_executable, Executable.export_library and
os.replace in your change.

In `@tilelang/carver/arch/driver/cuda_driver.py`:
- Around line 55-65: The Windows DLL search in _load_cudart currently includes a
dead "11" suffix; remove the "11" entry from the for-loop and replace the
hardcoded list with a more robust strategy: either (a) enumerate matching files
using a glob like "cudart64_*.dll" and pick the highest/first match, or (b)
derive the proper suffix from environment info (CUDA_PATH or nvcc --version) and
try that suffix first, falling back to a glob scan; ensure the function still
falls back to raising OSError if nothing is found.

In `@tilelang/contrib/msvc.py`:
- Around line 152-161: The _normalize_option function currently drops -std=...
and -g options silently; update _normalize_option to translate -std=<ver> into
MSVC's /std:<ver> (e.g., -std=c++17 -> /std:c++17) and map -g to an appropriate
MSVC debug flag (e.g., /Zi or /Z7) instead of returning None; locate and edit
the _normalize_option function in msvc.py and ensure it parses the suffix after
"-std=" and returns "/std:" + suffix, and replace the -g branch to return "/Zi"
(or "/Z7" if you prefer full debug info) so callers like the TVM/TileLang JIT
FFI glue that pass "-std=c++17" get equivalent MSVC flags emitted.

In `@tilelang/env.py`:
- Around line 47-53: Change the Windows platform check to use equality for
consistency: replace the condition using sys.platform.startswith("win32") with
sys.platform == "win32" in the block that builds
path_entries/normalized_entries/prepend_entries and updates os.environ["PATH"]
so it matches the other usage in this file (and continues to prepend TL_LIBS for
subprocesses).

In `@tilelang/jit/adapter/libgen.py`:
- Around line 63-66: The code currently builds library paths by calling
src.name.replace(".cu", _lib_ext) which can accidentally replace ".cu" anywhere
in the temp path; update the logic to replace only the file extension using
os.path.splitext on src.name (e.g., use os.path.splitext(src.name)[0] +
_lib_ext) for the .cu→.dll/.so case (around the src =
tempfile.NamedTemporaryFile(...) / libpath = ... block) and apply the same
change to the other occurrences where .cpp is converted to .so (the .cpp → .so
conversions later in the file) so extension replacement is robust on Windows and
other platforms.

In `@tilelang/libinfo.py`:
- Around line 8-22: Extract the CUDA subdirectory list into a single helper
(e.g., new function get_cuda_dll_subdirs or extend get_dll_directories to expose
the CUDA-only list) and have both tilelang/libinfo.py (get_dll_directories) and
tilelang/__init__.py call that helper instead of duplicating the literal list;
modify get_dll_directories to call the helper when sys.platform startswith
"win32" and update __init__.py to import and reuse that helper so the CUDA paths
are defined in one place (reference functions: get_dll_directories and the new
helper name you add).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4ee9b92c-2c4d-4b61-9ca4-4fcf36d442e7

📥 Commits

Reviewing files that changed from the base of the PR and between c2a85be and 9fd2134.

📒 Files selected for processing (35)
  • .gitattributes
  • .gitignore
  • CMakeLists.txt
  • cmake/FindPipCUDAToolkit.cmake
  • cmake/find_pip_cuda.py
  • cmake/generate_windows_import_lib.py
  • cmake/pypi-z3/FindZ3.cmake
  • docs/get_started/Installation.md
  • pyproject.toml
  • scripts/windows/uv-sync.cmd
  • src/layout/gemm_layouts.cc
  • src/op/operator.h
  • src/op/parallel.cc
  • src/runtime/logging.cc
  • src/target/codegen_cuda.cc
  • src/target/codegen_cuda.h
  • src/target/stubs/cuda.cc
  • src/target/stubs/cuda.h
  • src/target/stubs/cudart.cc
  • src/target/stubs/dynlib.h
  • src/target/stubs/nvrtc.cc
  • src/transform/common/pipeline_utils.h
  • src/transform/layout_inference.cc
  • src/transform/layout_reducer.cc
  • tilelang/__init__.py
  • tilelang/cache/kernel_cache.py
  • tilelang/carver/arch/driver/cuda_driver.py
  • tilelang/contrib/cc.py
  • tilelang/contrib/msvc.py
  • tilelang/contrib/nvcc.py
  • tilelang/env.py
  • tilelang/jit/adapter/libgen.py
  • tilelang/jit/adapter/tvm_ffi.py
  • tilelang/jit/adapter/wrapper.py
  • tilelang/libinfo.py
✅ Files skipped from review due to trivial changes (3)
  • .gitignore
  • .gitattributes
  • cmake/find_pip_cuda.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/transform/common/pipeline_utils.h
  • tilelang/jit/adapter/tvm_ffi.py
  • cmake/pypi-z3/FindZ3.cmake
  • pyproject.toml
  • src/runtime/logging.cc

Comment thread cmake/generate_windows_import_lib.py
Comment thread src/backend/cuda/codegen/stubs/nvrtc.cc
Comment thread src/transform/layout_reducer.cc
Comment thread tilelang/contrib/nvcc.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/runtime/logging.cc (1)

3-9: ⚠️ Potential issue | 🟡 Minor

Add missing #include <sstream> for std::istringstream and std::stringstream usage.

ParseSpec (line 85) uses std::istringstream, and VLogContext::str() (line 150) uses std::stringstream, but <sstream> is not explicitly included. While this currently compiles via transitive inclusion (likely from <tvm/runtime/logging.h>), relying on transitive includes is fragile and can break across different toolchains—particularly relevant for the MSVC compatibility work in this PR.

Proposed fix
 `#include` <algorithm>
 `#include` <cstdlib>
 `#include` <ctime>
 `#include` <iomanip>
 `#include` <iostream>
+#include <sstream>
 `#include` <string>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/logging.cc` around lines 3 - 9, Add an explicit include for
<sstream> because ParseSpec uses std::istringstream and VLogContext::str() uses
std::stringstream; update the includes at the top of src/runtime/logging.cc to
add `#include` <sstream> so the file no longer relies on transitive includes and
compiles reliably across toolchains (references: ParseSpec and
VLogContext::str()).
tilelang/jit/adapter/libgen.py (1)

146-154: ⚠️ Potential issue | 🟠 Major

Windows: NVCC subprocess is launched without the MSVC host-compiler environment.

subprocess.run(command, ...) is invoked without an explicit env, so on Windows the child process inherits whatever PATH the current Python process has. Unless the user started Python from a Visual Studio developer prompt, cl.exe will not be resolvable and NVCC will fail with "Cannot find compiler 'cl.exe' in PATH" — even though tilelang.contrib.nvcc.compile_cuda carefully threads get_nvcc_subprocess_env() through for exactly this reason.

Mirror the pattern from tilelang/contrib/nvcc.py so the MSVC toolchain (located via VsDevCmd.bat) is wired up for this NVCC invocation as well.

🔧 Proposed fix
+        compiler_env = None
+        if is_cuda_target(target) and sys.platform == "win32":
+            from tilelang.contrib.nvcc import get_nvcc_subprocess_env
+            compiler_env = get_nvcc_subprocess_env()
+
         try:
             if verbose:
                 print(f"compile_lib compilation command: {' '.join(command)}")
-            ret = subprocess.run(command, timeout=timeout)
+            ret = subprocess.run(command, timeout=timeout, env=compiler_env)
         except Exception as e:
             raise RuntimeError(f"Compile kernel failed because of {e}") from e
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/jit/adapter/libgen.py` around lines 146 - 154, The NVCC invocation
in the try block (the subprocess.run call in libgen.py) does not pass the MSVC
host-compiler environment; obtain the proper env by calling the same helper used
in tilelang.contrib.nvcc (get_nvcc_subprocess_env) and pass it to subprocess.run
as env=env so NVCC can locate cl.exe on Windows; update the try block around the
compile command (the code that prints "compile_lib compilation command" and
assigns ret = subprocess.run(...)) to compute env = get_nvcc_subprocess_env()
(importing that helper if needed) and call subprocess.run(command,
timeout=timeout, env=env).
♻️ Duplicate comments (4)
src/target/codegen_cuda.cc (1)

3591-3595: ⚠️ Potential issue | 🟠 Major

tl::__tan() now uses CUDAFastMath and reintroduces the __tanf precision regression.

This branch previously used CUDAFastMathTan, which deliberately falls back to tanf for float32 (see the comment on lines 173–174 in CUDAFastMathTan::operator()). With CUDAFastMath here, tan(float32) will emit __tanf, which was found to deviate too much from the reference implementation — that is exactly why CUDAFastMathTan exists as a separate functor.

🛠️ Suggested fix
   } else if (op->op.same_as(tl::__tan())) {
-    CUDAFastMath math_func;
+    CUDAFastMathTan math_func;
     std::string func_name = math_func(op->dtype, "tan");
     os << func_name << "(" << PrintExpr(op->args[0]) << ")";
     return true;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/target/codegen_cuda.cc` around lines 3591 - 3595, The tl::__tan() branch
incorrectly uses CUDAFastMath (which emits __tanf for float32) causing a
precision regression; replace CUDAFastMath with the specialized CUDAFastMathTan
functor in the tl::__tan() branch so the existing CUDAFastMathTan::operator()
behavior (falling back to tanf for float32 per its comment) is preserved —
update the instantiation and func_name generation to use CUDAFastMathTan instead
of CUDAFastMath and keep the PrintExpr(op->args[0]) usage and return true logic
intact.
src/target/stubs/nvrtc.cc (1)

73-76: ⚠️ Potential issue | 🟡 Minor

Error message still assumes PyTorch-with-CUDA is the only install path.

With this PR adding Windows and pip-installed CUDA toolkit support (nvidia-cuda-nvrtc wheels, standalone toolkits), reaching abort() here and telling users to "install PyTorch with CUDA" is misleading on those paths. Consider the broadened wording suggested on the previous review round.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/target/stubs/nvrtc.cc` around lines 73 - 76, The error message in
nvrtc.cc (the fprintf and abort path) incorrectly assumes PyTorch-with-CUDA is
the only install path; update the fprintf text in the nvrtc symbols-not-found
branch to use broader wording that mentions missing NVRTC/CUDA installation or
missing nvrtc symbols (e.g., "NVRTC symbols not found. Ensure a CUDA toolkit or
compatible NVRTC runtime is installed and accessible (for example via PyTorch
with CUDA, pip-installed nvidia-cuda-nvrtc wheels, or a system CUDA toolkit)"),
so the message covers Windows/pip standalone toolkits and other install methods
instead of only suggesting "install PyTorch with CUDA". Ensure the change is
made where fprintf(...) and the following abort() are called.
src/target/stubs/cudart.cc (1)

107-110: ⚠️ Potential issue | 🟡 Minor

Error message still only references PyTorch.

Same issue as previously flagged: on Windows and with pip-installed CUDA toolkit support added in this PR, this abort() path is also reachable without PyTorch, so the "install PyTorch with CUDA" hint is misleading. Broaden the wording as suggested in the previous review.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/target/stubs/cudart.cc` around lines 107 - 110, The error path prints a
message that only mentions PyTorch but abort() can be reached even when PyTorch
is not involved; update the fprintf call in cudart.cc (the call that prints
"TileLang Error: cudart symbols not found..." before abort()) to a broader,
accurate hint — e.g., instruct the user to ensure the CUDA runtime/toolkit is
available (mentioning options like installing PyTorch with CUDA or the CUDA
toolkit/pip CUDA support) and include the same context string "cudart symbols
not found" so the location is clear.
src/target/stubs/dynlib.h (1)

104-107: ⚠️ Potential issue | 🔴 Critical

_GNU_SOURCE is defined too late for TUs that include other system headers before dynlib.h.

Same concern as the previous review round: because <features.h> is latched by the first system header that includes it, any TU that transitively includes <stdlib.h> (etc.) before dynlib.h will see RTLD_DEFAULT / RTLD_NEXT undeclared even though they're used in dynlib_find_loaded(). The per-man-page rule is explicit: "The _GNU_SOURCE feature test macro must be defined in order to obtain the definitions of RTLD_DEFAULT and RTLD_NEXT from <dlfcn.h>". Prefer defining _GNU_SOURCE via target_compile_definitions(... PRIVATE _GNU_SOURCE=1) on the stub targets in CMake so it's active before any system header is processed.

🧹 Nitpick comments (5)
src/runtime/logging.cc (1)

94-129: Inconsistent return settings; after LOG(FATAL).

LogFatalImpl is [[noreturn]] (line 62), so the return settings; statements following the LOG(FATAL) branches at lines 110, 115, and 123 are unreachable. The matching branch at lines 100–102 correctly omits the redundant return. Consider dropping the dead returns for consistency (or, conversely, guard with a comment if some callers catch the thrown InternalError and you want a deterministic fallback — in which case the behavior should be the same across all four FATAL branches, including the "empty filename" one).

🧹 Proposed cleanup
     if (!std::getline(spec_stream, level, ',')) {
       LOG(FATAL) << "TVM_LOG_DEBUG ill-formed at position " << tell_pos(level)
                  << ": expecting \"=<level>\" after \"" << name << "\"";
-      return settings;
     }
     if (level.empty()) {
       LOG(FATAL) << "TVM_LOG_DEBUG ill-formed at position " << tell_pos(level)
                  << ": empty level after \"" << name << "\"";
-      return settings;
     }

     char *end_of_level = nullptr;
     int level_val = static_cast<int>(strtol(level.c_str(), &end_of_level, 10));
     if (end_of_level != level.c_str() + level.size()) {
       LOG(FATAL) << "TVM_LOG_DEBUG ill-formed at position " << tell_pos(level)
                  << ": invalid level: \"" << level << "\"";
-      return settings;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/logging.cc` around lines 94 - 129, The LOG(FATAL) calls inside
the while loop invoke LogFatalImpl which is [[noreturn]], so remove the
unreachable "return settings;" statements that follow the LOG(FATAL) branches in
the loop (the returns after the "expecting \"=<level>\"", "empty level after",
and "invalid level" checks) to match the earlier branch that omits a return;
alternatively, if you prefer explicit clarity, replace those returns with a
brief comment referencing LogFatalImpl's [[noreturn]] behavior (e.g., "//
LogFatalImpl is [[noreturn]] - no return needed") so all FATAL branches are
handled consistently.
tilelang/libinfo.py (1)

49-56: Minor: for…else here is functionally equivalent to placing the raise unindented.

Since the inner loop uses return (not break), the outer for always completes "normally" when no match is found, so the else clause is entered on every miss. This is correct, but the else adds no semantic distinction from a plain trailing raise, and it's one of the more commonly misread Python constructs. Optional cleanup for readability.

♻️ Suggestion
     for lib_root in TL_LIBS:
         for lib_name in lib_names:
             lib_dll_path = os.path.join(lib_root, lib_name)
             if os.path.exists(lib_dll_path) and os.path.isfile(lib_dll_path):
                 return lib_dll_path
-    else:
-        message = f"Cannot find libraries: {', '.join(lib_names)}\n" + "List of candidates:\n" + "\n".join(TL_LIBS)
-        raise RuntimeError(message)
+    message = (
+        f"Cannot find libraries: {', '.join(lib_names)}\n"
+        "List of candidates:\n" + "\n".join(TL_LIBS)
+    )
+    raise RuntimeError(message)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/libinfo.py` around lines 49 - 56, The for...else after iterating
TL_LIBS and lib_names is unnecessary and potentially confusing because the inner
loop returns on a match; replace the for...else by unindenting the raise so it
follows the nested loops (i.e., after the two loops complete) — locate the loops
over TL_LIBS and lib_names and move the RuntimeError raise (which builds the
message from lib_names and TL_LIBS and references lib_dll_path/lib_root) out of
the else block and place it directly after the loops.
tilelang/__init__.py (2)

180-190: Consider delegating CUDA DLL directory enumeration to libinfo.get_dll_directories() to remove duplication.

The CUDA directory list here (cuda_home, bin, bin/x86_64, lib/x64, nvvm/bin) duplicates the logic in tilelang/libinfo.py::get_dll_directories() (lines 8–22), which is also invoked later in _load_tile_lang_lib(). Any future change (e.g., adding libnvvp, adjusting for CUDA 13 layouts) would need to be made in two places. Importing libinfo earlier and reusing its output would keep the two callsites in sync and avoid maintaining parallel path lists.

♻️ Sketch
         if sys.platform.startswith("win32") and sys.version_info >= (3, 8):
             _prepend_windows_runtime_paths()
+            from . import libinfo as _libinfo_for_dll
             _dll_handles = []
-            cuda_home = getattr(env, "CUDA_HOME", "")
-            if cuda_home:
-                for path in (
-                    cuda_home,
-                    os.path.join(cuda_home, "bin"),
-                    os.path.join(cuda_home, "bin", "x86_64"),
-                    os.path.join(cuda_home, "lib", "x64"),
-                    os.path.join(cuda_home, "nvvm", "bin"),
-                ):
-                    if os.path.isdir(path):
-                        _dll_handles.append(os.add_dll_directory(path))
+            for path in _libinfo_for_dll.get_dll_directories():
+                _dll_handles.append(os.add_dll_directory(path))

Note that _load_tile_lang_lib() at lines 201–203 calls os.add_dll_directory(path) without retaining the returned cookies, so those entries are eligible for garbage collection as soon as the loop exits; the handles kept in _dll_handles here are what actually keep the CUDA paths alive. Consolidating the enumeration would also make that ownership explicit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/__init__.py` around lines 180 - 190, Replace the duplicated CUDA
path list with a call to libinfo.get_dll_directories(): import tilelang.libinfo
(or the module where get_dll_directories lives), call get_dll_directories()
instead of constructing the tuple of cuda_home paths, and iterate its returned
paths appending each os.add_dll_directory return value to the existing
_dll_handles list; also update _load_tile_lang_lib() so it does not call
os.add_dll_directory(path) without retaining the returned handle—reuse or extend
_dll_handles there to maintain ownership.

115-147: Silent try/except Exception: pass on tvm_ffi path lookup will mask real failures.

When from tvm_ffi import libinfo or find_libtvm_ffi() raises something unexpected (e.g., a packaging/version skew that installs a broken tvm_ffi), Windows users will silently end up with runtime_paths missing the libtvm_ffi directory and no indication of why. A debug-level log would preserve the non-fatal behavior while giving a breadcrumb when DLL loading later fails.

🔧 Suggestion
     try:
         from tvm_ffi import libinfo as tvm_ffi_libinfo

         runtime_paths.append(os.path.dirname(tvm_ffi_libinfo.find_libtvm_ffi()))
-    except Exception:
-        pass
+    except Exception as exc:  # noqa: BLE001
+        logger.debug("tvm_ffi libinfo unavailable for PATH setup: %r", exc)

As per static analysis hint Ruff S110/BLE001 on lines 125–126.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/__init__.py` around lines 115 - 147, The silent except in
_prepend_windows_runtime_paths around "from tvm_ffi import libinfo as
tvm_ffi_libinfo" and find_libtvm_ffi() should be changed to capture and log the
exception at debug level so failures aren't masked: add a module logger via
logging.getLogger(__name__) and replace "except Exception: pass" with "except
Exception as e:" and call logger.debug with a descriptive message (including the
exception and exc_info=True) referencing tvm_ffi and
tvm_ffi_libinfo.find_libtvm_ffi to help trace why the runtime path lookup failed
while preserving the non-fatal behavior.
tilelang/contrib/msvc.py (1)

288-290: Add get_target_triple attribute to match the fcompile contract used by tilelang/contrib/cc.py.

The cc.py::create_shared function sets both output_format and get_target_triple attributes. While TVM's cross_compiler() defensively checks for this attribute via hasattr() before accessing it, and the MSVC create_shared is also sometimes wrapped through that path, matching this interface pattern ensures consistency and prevents future issues if direct attribute access occurs without defensive checks.

Optional addition
 create_shared.output_format = "dll"
+create_shared.get_target_triple = lambda: None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/contrib/msvc.py` around lines 288 - 290, The create_shared callable
in the MSVC backend lacks the get_target_triple attribute expected by the
fcompile contract; add a get_target_triple attribute on create_shared (matching
the pattern used in cc.py::create_shared) that returns the target triple from
the passed config/context so consumers can call
create_shared.get_target_triple(cfg) consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/windows/uv-sync.cmd`:
- Around line 7-15: The argument forwarding in the :parse_args block currently
builds SYNC_ARGS using %1 which loses original quoting and breaks values with
spaces; update the concatenation to append the quoted, unescaped token such as
"%~1" instead of %1 so SYNC_ARGS preserves spaces and quotes when passed to the
downstream `uv sync` command, leaving the rest of the parsing (NO_NVCC handling
and shift/goto parse_args) unchanged.

In `@src/target/stubs/dynlib.h`:
- Around line 122-136: dynlib_find_loaded currently returns the raw handle
(NULL) when dlsym(RTLD_DEFAULT, ...) succeeds on glibc, which callers interpret
as "not found"; change dynlib_find_loaded to return a non-null sentinel (e.g. a
unique pointer value like SENTINEL_RTLD_DEFAULT) when dlsym(RTLD_DEFAULT, ...)
finds the symbol (and likewise a different sentinel for RTLD_NEXT if you check
it), and update dynlib_sym to translate that sentinel back to RTLD_DEFAULT (or
RTLD_NEXT) when resolving the symbol so the real dlsym is invoked with the
correct special handle; reference dynlib_find_loaded and dynlib_sym to locate
where to insert the sentinel logic and the translation logic.

In `@tilelang/carver/arch/driver/cuda_driver.py`:
- Around line 55-64: Remove the bogus "11" candidate and stop using
ctypes.windll for loading the CUDA runtime in _load_cudart(); update the Windows
candidate tuple (e.g., keep "13", "12", "110", etc. but remove "11") and call
ctypes.cdll.LoadLibrary(...) instead of ctypes.windll.LoadLibrary so the loader
uses the cdecl convention and matches non-Windows behavior; ensure the function
still falls back to raising OSError if no candidate loads and retains the
non-Windows path that uses ctypes.cdll.LoadLibrary("libcudart.so").

In `@tilelang/jit/adapter/libgen.py`:
- Around line 64-66: The temp file path is being mangled by
src.name.replace(".cu", _lib_ext) which can replace ".cu" occurrences in parent
directories; change to using os.path.splitext on src.name to swap the extension
(e.g., base, _ = os.path.splitext(src.name); libpath = base + _lib_ext) so only
the filename extension is replaced. Update the same pattern in the hip and cpu
branches (the other replace usages around the sections handling those targets)
and keep references to src (NamedTemporaryFile), libpath, and _lib_ext when
making the change.

---

Outside diff comments:
In `@src/runtime/logging.cc`:
- Around line 3-9: Add an explicit include for <sstream> because ParseSpec uses
std::istringstream and VLogContext::str() uses std::stringstream; update the
includes at the top of src/runtime/logging.cc to add `#include` <sstream> so the
file no longer relies on transitive includes and compiles reliably across
toolchains (references: ParseSpec and VLogContext::str()).

In `@tilelang/jit/adapter/libgen.py`:
- Around line 146-154: The NVCC invocation in the try block (the subprocess.run
call in libgen.py) does not pass the MSVC host-compiler environment; obtain the
proper env by calling the same helper used in tilelang.contrib.nvcc
(get_nvcc_subprocess_env) and pass it to subprocess.run as env=env so NVCC can
locate cl.exe on Windows; update the try block around the compile command (the
code that prints "compile_lib compilation command" and assigns ret =
subprocess.run(...)) to compute env = get_nvcc_subprocess_env() (importing that
helper if needed) and call subprocess.run(command, timeout=timeout, env=env).

---

Duplicate comments:
In `@src/target/codegen_cuda.cc`:
- Around line 3591-3595: The tl::__tan() branch incorrectly uses CUDAFastMath
(which emits __tanf for float32) causing a precision regression; replace
CUDAFastMath with the specialized CUDAFastMathTan functor in the tl::__tan()
branch so the existing CUDAFastMathTan::operator() behavior (falling back to
tanf for float32 per its comment) is preserved — update the instantiation and
func_name generation to use CUDAFastMathTan instead of CUDAFastMath and keep the
PrintExpr(op->args[0]) usage and return true logic intact.

In `@src/target/stubs/cudart.cc`:
- Around line 107-110: The error path prints a message that only mentions
PyTorch but abort() can be reached even when PyTorch is not involved; update the
fprintf call in cudart.cc (the call that prints "TileLang Error: cudart symbols
not found..." before abort()) to a broader, accurate hint — e.g., instruct the
user to ensure the CUDA runtime/toolkit is available (mentioning options like
installing PyTorch with CUDA or the CUDA toolkit/pip CUDA support) and include
the same context string "cudart symbols not found" so the location is clear.

In `@src/target/stubs/nvrtc.cc`:
- Around line 73-76: The error message in nvrtc.cc (the fprintf and abort path)
incorrectly assumes PyTorch-with-CUDA is the only install path; update the
fprintf text in the nvrtc symbols-not-found branch to use broader wording that
mentions missing NVRTC/CUDA installation or missing nvrtc symbols (e.g., "NVRTC
symbols not found. Ensure a CUDA toolkit or compatible NVRTC runtime is
installed and accessible (for example via PyTorch with CUDA, pip-installed
nvidia-cuda-nvrtc wheels, or a system CUDA toolkit)"), so the message covers
Windows/pip standalone toolkits and other install methods instead of only
suggesting "install PyTorch with CUDA". Ensure the change is made where
fprintf(...) and the following abort() are called.

---

Nitpick comments:
In `@src/runtime/logging.cc`:
- Around line 94-129: The LOG(FATAL) calls inside the while loop invoke
LogFatalImpl which is [[noreturn]], so remove the unreachable "return settings;"
statements that follow the LOG(FATAL) branches in the loop (the returns after
the "expecting \"=<level>\"", "empty level after", and "invalid level" checks)
to match the earlier branch that omits a return; alternatively, if you prefer
explicit clarity, replace those returns with a brief comment referencing
LogFatalImpl's [[noreturn]] behavior (e.g., "// LogFatalImpl is [[noreturn]] -
no return needed") so all FATAL branches are handled consistently.

In `@tilelang/__init__.py`:
- Around line 180-190: Replace the duplicated CUDA path list with a call to
libinfo.get_dll_directories(): import tilelang.libinfo (or the module where
get_dll_directories lives), call get_dll_directories() instead of constructing
the tuple of cuda_home paths, and iterate its returned paths appending each
os.add_dll_directory return value to the existing _dll_handles list; also update
_load_tile_lang_lib() so it does not call os.add_dll_directory(path) without
retaining the returned handle—reuse or extend _dll_handles there to maintain
ownership.
- Around line 115-147: The silent except in _prepend_windows_runtime_paths
around "from tvm_ffi import libinfo as tvm_ffi_libinfo" and find_libtvm_ffi()
should be changed to capture and log the exception at debug level so failures
aren't masked: add a module logger via logging.getLogger(__name__) and replace
"except Exception: pass" with "except Exception as e:" and call logger.debug
with a descriptive message (including the exception and exc_info=True)
referencing tvm_ffi and tvm_ffi_libinfo.find_libtvm_ffi to help trace why the
runtime path lookup failed while preserving the non-fatal behavior.

In `@tilelang/contrib/msvc.py`:
- Around line 288-290: The create_shared callable in the MSVC backend lacks the
get_target_triple attribute expected by the fcompile contract; add a
get_target_triple attribute on create_shared (matching the pattern used in
cc.py::create_shared) that returns the target triple from the passed
config/context so consumers can call create_shared.get_target_triple(cfg)
consistently.

In `@tilelang/libinfo.py`:
- Around line 49-56: The for...else after iterating TL_LIBS and lib_names is
unnecessary and potentially confusing because the inner loop returns on a match;
replace the for...else by unindenting the raise so it follows the nested loops
(i.e., after the two loops complete) — locate the loops over TL_LIBS and
lib_names and move the RuntimeError raise (which builds the message from
lib_names and TL_LIBS and references lib_dll_path/lib_root) out of the else
block and place it directly after the loops.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 306edc71-c1f8-489a-8454-1a75b1561156

📥 Commits

Reviewing files that changed from the base of the PR and between 9fd2134 and 452c7d6.

📒 Files selected for processing (32)
  • .gitattributes
  • .gitignore
  • CMakeLists.txt
  • cmake/FindPipCUDAToolkit.cmake
  • cmake/find_pip_cuda.py
  • cmake/generate_windows_import_lib.py
  • cmake/pypi-z3/FindZ3.cmake
  • docs/get_started/Installation.md
  • pyproject.toml
  • scripts/windows/uv-sync.cmd
  • src/layout/gemm_layouts.cc
  • src/op/operator.h
  • src/runtime/logging.cc
  • src/target/codegen_cuda.cc
  • src/target/codegen_cuda.h
  • src/target/stubs/cuda.cc
  • src/target/stubs/cuda.h
  • src/target/stubs/cudart.cc
  • src/target/stubs/dynlib.h
  • src/target/stubs/nvrtc.cc
  • src/transform/layout_reducer.cc
  • tilelang/__init__.py
  • tilelang/cache/kernel_cache.py
  • tilelang/carver/arch/driver/cuda_driver.py
  • tilelang/contrib/cc.py
  • tilelang/contrib/msvc.py
  • tilelang/contrib/nvcc.py
  • tilelang/env.py
  • tilelang/jit/adapter/libgen.py
  • tilelang/jit/adapter/tvm_ffi.py
  • tilelang/jit/adapter/wrapper.py
  • tilelang/libinfo.py
✅ Files skipped from review due to trivial changes (5)
  • .gitattributes
  • .gitignore
  • cmake/pypi-z3/FindZ3.cmake
  • pyproject.toml
  • CMakeLists.txt
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/target/codegen_cuda.h
  • tilelang/jit/adapter/wrapper.py
  • cmake/FindPipCUDAToolkit.cmake
  • src/target/stubs/cuda.cc

Comment thread scripts/windows/uv-sync.cmd Outdated
Comment thread src/backend/cuda/codegen/stubs/dynlib.h
Comment thread tilelang/carver/arch/driver/cuda_driver.py
Comment thread tilelang/jit/adapter/libgen.py

@SiriusNEO SiriusNEO left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good, but we don't have a Windows CI to test it. Maybe we could test it locally? And left some comments.

Comment thread docs/get_started/Installation.md Outdated
Comment thread tilelang/libinfo.py Outdated
Comment thread docs/get_started/Installation.md Outdated
Comment thread docs/get_started/Installation.md Outdated
Comment thread docs/get_started/Installation.md Outdated
Comment thread src/backend/cuda/codegen/codegen_cuda.cc

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tilelang/cache/kernel_cache.py (1)

91-107: ⚠️ Potential issue | 🟡 Minor

Library stamp still hashes only a single DLL despite expanded lib_names list.

The loop returns on the first match (line 106), so adding tvm.dll and tvm_ffi.dll to lib_names doesn't actually include them in the stamp when tilelang.dll exists — which is the common case. If the intent (per the AI summary) is to invalidate the cache when any of tilelang/tvm/tvm_ffi changes, the stamp needs to hash all found libraries and combine the digests.

♻️ Suggested change
-        for lib_dir in lib_dirs:
-            for name in lib_names:
-                path = os.path.join(lib_dir, name)
-                if os.path.exists(path):
-                    file_hash = sha256()
-                    with open(path, "rb") as f:
-                        for chunk in iter(lambda: f.read(1 << 20), b""):
-                            file_hash.update(chunk)
-                    return f"{name}:{file_hash.hexdigest()}"
-        return None
+        parts: list[str] = []
+        for name in lib_names:
+            for lib_dir in lib_dirs:
+                path = os.path.join(lib_dir, name)
+                if os.path.exists(path):
+                    file_hash = sha256()
+                    with open(path, "rb") as f:
+                        for chunk in iter(lambda: f.read(1 << 20), b""):
+                            file_hash.update(chunk)
+                    parts.append(f"{name}:{file_hash.hexdigest()}")
+                    break
+        return "|".join(parts) if parts else None

If only tilelang.dll is meant to stamp the cache (and tvm.dll/tvm_ffi.dll are only fallbacks when TileLang isn't located), please document that intent — the current list ordering makes the added names effectively dead code in the normal install layout.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/cache/kernel_cache.py` around lines 91 - 107, The current loop over
lib_names returns on the first match so the stamp only reflects a single
library; change the logic in the kernel cache routine that builds the library
stamp (use the existing lib_names, lib_dirs, sha256 variables) to collect all
matching library files instead of returning immediately, compute each file's
SHA-256 (as done with file_hash.update(...)) and combine their hex digests
(e.g., concatenate with a separator or hash the concatenation) into a single
stamp string (replace the current return f"{name}:{file_hash.hexdigest()}" with
a combined digest for all found libs), and still return None if no libraries are
found; ensure the symbol names lib_names, lib_dirs, sha256, and
file_hash.hexdigest() are used to locate and update the code.
♻️ Duplicate comments (4)
tilelang/carver/arch/driver/cuda_driver.py (1)

55-64: ⚠️ Potential issue | 🟡 Minor

Bogus "11" DLL candidate and ctypes.windll usage still unresolved.

Previously flagged concerns still apply:

  1. cudart64_11.dll does not exist in any CUDA release — Windows CUDA runtime DLLs use patterns like cudart64_110.dll, cudart64_12.dll, cudart64_130_96.dll, etc. The "11" entry is dead code.
  2. ctypes.windll.LoadLibrary returns a WinDLL (stdcall default prototype), while the CUDA Runtime API is __cdecl. Harmless on x64 today, but ctypes.cdll.LoadLibrary is semantically correct and keeps Windows/Linux paths consistent.

Additionally, note the error-handling contract mismatch with the C++ side: src/target/stubs/cudart.cc (TryLoadLibCudart) aborts on failure, whereas this Python helper raises OSError. The caller get_device_attribute swallows it via the broad except Exception on Line 85, so behavior is fine here — just worth being aware of if this helper is reused elsewhere.

♻️ Proposed change
 def _load_cudart():
     """Load the CUDA runtime library, searching for the version-suffixed DLL on Windows."""
     if sys.platform == "win32":
-        for ver in ("13", "12", "110", "11"):
+        for ver in ("13", "12", "110"):
             try:
-                return ctypes.windll.LoadLibrary(f"cudart64_{ver}.dll")
+                return ctypes.cdll.LoadLibrary(f"cudart64_{ver}.dll")
             except OSError:
                 continue
         raise OSError("Cannot find cudart64_*.dll")
     return ctypes.cdll.LoadLibrary("libcudart.so")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/carver/arch/driver/cuda_driver.py` around lines 55 - 64, The
_load_cudart function currently tries a bogus "11" DLL name and uses
ctypes.windll (WinDLL) which is semantically incorrect for the cdecl CUDA
runtime; update _load_cudart to (1) replace the Windows candidate list with real
CUDA runtime names (e.g. "cudart64_130_96.dll", "cudart64_130.dll",
"cudart64_12.dll", "cudart64_110.dll" or similar valid suffixes) instead of
"11", and (2) call ctypes.cdll.LoadLibrary on Windows as well (use
ctypes.cdll.LoadLibrary(f"...") inside the Windows loop) so the runtime is
loaded with cdecl semantics; keep raising OSError on not found so callers like
get_device_attribute that catch exceptions continue to behave as before.
tilelang/contrib/msvc.py (2)

175-175: ⚠️ Potential issue | 🟡 Minor

Pass usedforsecurity=False to hashlib.md5().

The digest is only a deterministic filename prefix; marking it non-security-sensitive silences Ruff S324 and documents intent on FIPS-restricted systems where md5 is otherwise blocked.

🛠️ Proposed fix
-    prefix = hashlib.md5(os.path.dirname(os.path.abspath(path)).encode()).hexdigest()[:8]
+    prefix = hashlib.md5(
+        os.path.dirname(os.path.abspath(path)).encode(), usedforsecurity=False
+    ).hexdigest()[:8]

As per static-analysis hint Ruff S324 on line 175.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/contrib/msvc.py` at line 175, The MD5 usage for generating the
filename prefix should be marked non-security-sensitive: modify the call to
hashlib.md5(...) used when computing the variable prefix so it passes
usedforsecurity=False to hashlib.md5 (i.e., update the hashlib.md5(...)
invocation in the prefix assignment in msvc.py) to silence Ruff S324 and
document intent on FIPS-restricted systems.

113-145: ⚠️ Potential issue | 🟠 Major

Still caching/returning base_env on MSVC setup failure.

Lines 129, 135, 140 continue to cache and return base_env when VsDevCmd discovery/import fails or cl.exe is still not on the resulting PATH. Callers (create_shared) then re-probe via shutil.which("cl.exe", path=get_env_path(compiler_env or {})), so on a machine where cl.exe happens to be on the user's default PATH despite VsDevCmd failure, compilation proceeds with an environment that is missing INCLUDE/LIB/SDK variables and fails later with a far less clear error. Returning None here (and leaving the explanation in _MSVC_ENV_ERROR) lets the caller surface the detail in its RuntimeError message.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/contrib/msvc.py` around lines 113 - 145, The function
get_msvc_subprocess_env currently caches and returns base_env on failures, which
masks missing MSVC vars; instead ensure that on any VsDevCmd discovery/import
failure or when cl.exe is not found in the compiler_env you set _MSVC_ENV_CACHE
to None (not base_env) and set _MSVC_ENV_ERROR accordingly, then return None so
callers (e.g., create_shared) can surface the detailed error; update the
branches that currently assign _MSVC_ENV_CACHE = base_env (the lines handling
missing vsdevcmd, import failure, and missing cl.exe) to assign None and return
None while preserving the _MSVC_ENV_ERROR message.
cmake/FindPipCUDAToolkit.cmake (1)

283-285: ⚠️ Potential issue | 🟠 Major

Linker-flag cache still grows on every re-configure.

CMAKE_{EXE,SHARED,MODULE}_LINKER_FLAGS are still being prepended and re-cached with FORCE each time _tilelang_activate_msvc_env runs. Unlike the include flags (guarded by _TILELANG_INCLUDE_FLAGS_APPLIED on line 224), these three still lack an idempotency guard, so repeated cmake invocations keep accumulating /LIBPATH:… fragments until MSVC's command-line length limits start biting.

🔧 Proposed guard
-    set(CMAKE_EXE_LINKER_FLAGS_INIT "${_tilelang_libpath_flags} ${CMAKE_EXE_LINKER_FLAGS_INIT}")
-    set(CMAKE_SHARED_LINKER_FLAGS_INIT "${_tilelang_libpath_flags} ${CMAKE_SHARED_LINKER_FLAGS_INIT}")
-    set(CMAKE_MODULE_LINKER_FLAGS_INIT "${_tilelang_libpath_flags} ${CMAKE_MODULE_LINKER_FLAGS_INIT}")
-    set(CMAKE_EXE_LINKER_FLAGS "${_tilelang_libpath_flags} ${CMAKE_EXE_LINKER_FLAGS}" CACHE STRING "Executable linker flags" FORCE)
-    set(CMAKE_SHARED_LINKER_FLAGS "${_tilelang_libpath_flags} ${CMAKE_SHARED_LINKER_FLAGS}" CACHE STRING "Shared linker flags" FORCE)
-    set(CMAKE_MODULE_LINKER_FLAGS "${_tilelang_libpath_flags} ${CMAKE_MODULE_LINKER_FLAGS}" CACHE STRING "Module linker flags" FORCE)
+    if(NOT DEFINED _TILELANG_LIBPATH_FLAGS_APPLIED)
+      set(CMAKE_EXE_LINKER_FLAGS_INIT "${_tilelang_libpath_flags} ${CMAKE_EXE_LINKER_FLAGS_INIT}")
+      set(CMAKE_SHARED_LINKER_FLAGS_INIT "${_tilelang_libpath_flags} ${CMAKE_SHARED_LINKER_FLAGS_INIT}")
+      set(CMAKE_MODULE_LINKER_FLAGS_INIT "${_tilelang_libpath_flags} ${CMAKE_MODULE_LINKER_FLAGS_INIT}")
+      set(CMAKE_EXE_LINKER_FLAGS "${_tilelang_libpath_flags} ${CMAKE_EXE_LINKER_FLAGS}" CACHE STRING "Executable linker flags" FORCE)
+      set(CMAKE_SHARED_LINKER_FLAGS "${_tilelang_libpath_flags} ${CMAKE_SHARED_LINKER_FLAGS}" CACHE STRING "Shared linker flags" FORCE)
+      set(CMAKE_MODULE_LINKER_FLAGS "${_tilelang_libpath_flags} ${CMAKE_MODULE_LINKER_FLAGS}" CACHE STRING "Module linker flags" FORCE)
+      set(_TILELANG_LIBPATH_FLAGS_APPLIED TRUE CACHE INTERNAL "")
+    endif()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/FindPipCUDAToolkit.cmake` around lines 283 - 285, The linker flag lines
in _tilelang_activate_msvc_env repeatedly prepend and FORCE-cache into
CMAKE_EXE_LINKER_FLAGS, CMAKE_SHARED_LINKER_FLAGS and CMAKE_MODULE_LINKER_FLAGS
on every configure, causing accumulation; add an idempotency guard (e.g.
check/set a variable like _TILELANG_LINKER_FLAGS_APPLIED) around those three
set(...) calls so they only run once (mirroring the existing
_TILELANG_INCLUDE_FLAGS_APPLIED pattern used for include flags) and prevent
repeated prepends on subsequent cmake runs.
🧹 Nitpick comments (10)
src/layout/gemm_layouts.cc (1)

14-21: Consider centralizing the unreachable macro in a shared header.

If other translation units need the same "unreachable" semantics (the PR description mentions MSVC portability fixes across several files), defining TILELANG_COMPILER_UNREACHABLE locally here will lead to duplication. Consider moving it to a common internal header (e.g., a tl_common.h or similar) so it can be reused consistently across the codebase.

Minor note: the ((void)0) fallback does not suppress "control reaches end of non-void function" warnings on exotic compilers that define none of _MSC_VER, __GNUC__, or __clang__ — acceptable given those are effectively all mainstream toolchains today.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/layout/gemm_layouts.cc` around lines 14 - 21, The
TILELANG_COMPILER_UNREACHABLE macro is being defined locally in gemm_layouts.cc
which duplicates behavior across translation units; move this definition into a
shared internal header (e.g., tl_common.h or internal/common.h) and replace the
local definition with an `#include` of that header in gemm_layouts.cc and other
files that need it. Ensure the macro is wrapped with include guards or a `#ifndef`
TILELANG_COMPILER_UNREACHABLE check in the shared header to avoid redefinition,
and preserve the same platform checks (__MSC_VER, __GNUC__, __clang__) and
fallback semantics when migrating.
src/op/parallel.cc (1)

85-93: Defensive optional/cast guarding is correct and consistent with the writer.

The cast target Map<Var, ReducerInfo> matches what ReducerLayoutAnnotator::VisitStmt_(ForNode) in src/transform/layout_reducer.cc writes to attr::kReducerInfo (inside_reducer_range_ is declared as Map<Var, ReducerInfo>). The two-step guard (outer Optional<Any> then inner as<Map<Var, ReducerInfo>>()) avoids the MSVC debug std::optional assertion when the annotation is absent, and also tolerates the annotation being present with a different payload type.

One small tradeoff worth noting: a silent fallback on a failed cast will mask an unexpected payload type rather than surface it. If in practice only this writer can produce the For-node annotation, consider either (a) leaving a DLOG(WARNING) in the else-branch of the inner if, or (b) keeping an ICHECK on the inner cast to catch regressions, while still guarding the outer optional. Feel free to ignore if the tolerant behavior is intentional (matching the kLayoutMap handling in layout_reducer.cc).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/op/parallel.cc` around lines 85 - 93, The current two-step guard around
op->annotations.Get(attr::kReducerInfo) correctly avoids MSVC optional
assertions but silently ignores a mismatched payload type; update the inner
branch that attempts reducer_info_ref.value().as<Map<Var, ReducerInfo>>() so
that if the cast fails you emit a diagnostic (e.g., DLOG(WARNING) with the
annotation contents or type) rather than silently skipping it—leave the outer
optional guard in place and still populate p->reducer_info_map_.Set(buffer,
info) when the cast succeeds; alternatively, if you prefer failing fast during
development, replace the silent path with an ICHECK on the inner cast to surface
regressions (this mirrors the writer in
ReducerLayoutAnnotator::VisitStmt_(ForNode) which produces Map<Var,
ReducerInfo>).
tilelang/libinfo.py (1)

49-56: for ... else with an inner return is harder to read than a plain post-loop raise.

Since the inner loop never breaks, the else: clause runs exactly when the nested loops complete without a match — equivalent to placing the raise directly after the outer for. Dropping the else makes the control flow more obvious.

♻️ Proposed simplification
     for lib_root in TL_LIBS:
         for lib_name in lib_names:
             lib_dll_path = os.path.join(lib_root, lib_name)
             if os.path.exists(lib_dll_path) and os.path.isfile(lib_dll_path):
                 return lib_dll_path
-    else:
-        message = f"Cannot find libraries: {', '.join(lib_names)}\n" + "List of candidates:\n" + "\n".join(TL_LIBS)
-        raise RuntimeError(message)
+    message = (
+        f"Cannot find libraries: {', '.join(lib_names)}\n"
+        "List of candidates:\n" + "\n".join(TL_LIBS)
+    )
+    raise RuntimeError(message)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/libinfo.py` around lines 49 - 56, The for...else is unnecessary
because the inner loop returns on match and never breaks, so simplify control
flow by removing the else and placing the RuntimeError raise after the outer
loop; specifically in tilelang.libinfo (the loop iterating TL_LIBS and lib_names
that builds lib_dll_path), keep the nested iteration and the existing return
when os.path.exists/os.path.isfile succeed, then after both loops complete raise
the RuntimeError with the same message using TL_LIBS and lib_names.
cmake/find_pip_cuda.py (2)

186-197: Silent sys.exit(1) on failure hides the root cause from CMake.

main() distinguishes "cu_dir not resolved" (line 192) from "nvcc missing inside cu_dir" (line 196) but emits no diagnostic in either case. _tilelang_run_find_pip_cuda in cmake/FindPipCUDAToolkit.cmake only sees result_EQUAL 0 vs non-zero, so when WITH_PIP_CUDA_TOOLCHAIN is set to a path where cu_dir was resolved but nvcc happens to be missing, the user sees the generic "no pip-installed CUDA toolkit could be resolved" message rather than the actionable "resolved cu<ver> directory X but bin/nvcc is missing". Consider writing a short message to sys.stderr in each branch so it surfaces via execute_process stderr.

♻️ Proposed diff
-    if cu_dir is None:
-        sys.exit(1)
-
-    nvcc = _find_nvcc(cu_dir)
-    if nvcc is None:
-        sys.exit(1)
+    if cu_dir is None:
+        print("find_pip_cuda: could not resolve a pip-installed CUDA cu<ver> directory", file=sys.stderr)
+        sys.exit(1)
+
+    nvcc = _find_nvcc(cu_dir)
+    if nvcc is None:
+        print(f"find_pip_cuda: resolved {cu_dir} but nvcc was not found under bin/", file=sys.stderr)
+        sys.exit(1)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/find_pip_cuda.py` around lines 186 - 197, The main() currently exits
silently on failures; update it to write explicit error diagnostics to
sys.stderr before calling sys.exit(1): when cu_dir is None (from _find_cu_dir())
print a clear message like "could not resolve cu<ver> directory" with context,
and when nvcc is None (after calling _find_nvcc(cu_dir)) print a message that
the resolved cu_dir exists but bin/nvcc is missing (include the cu_dir path).
Ensure messages are short, go to sys.stderr so CMake's execute_process will
surface them, and keep using sys.exit(1) after printing.

28-41: Redundant fallback probe in _find_nvcc.

On each platform, one of the two fallback branches checks the same filename as candidate (e.g., on Windows _nvcc_name() is already nvcc.exe, making lines 37‑39 redundant; on POSIX _nvcc_name() is nvcc, making lines 33‑35 redundant). The logic still works but the duplication is easy to misread.

♻️ Proposed simplification
 def _find_nvcc(cu_dir: pathlib.Path) -> pathlib.Path | None:
-    candidate = cu_dir / "bin" / _nvcc_name()
-    if candidate.is_file():
-        return candidate
-
-    fallback = cu_dir / "bin" / "nvcc"
-    if fallback.is_file():
-        return fallback
-
-    fallback_exe = cu_dir / "bin" / "nvcc.exe"
-    if fallback_exe.is_file():
-        return fallback_exe
-
-    return None
+    bin_dir = cu_dir / "bin"
+    for name in (_nvcc_name(), "nvcc", "nvcc.exe"):
+        candidate = bin_dir / name
+        if candidate.is_file():
+            return candidate
+    return None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/find_pip_cuda.py` around lines 28 - 41, The _find_nvcc function
currently probes the same filename multiple times because _nvcc_name() already
returns platform-specific names; simplify by building an ordered list of unique
filenames to check (start with _nvcc_name(), then add "nvcc" and "nvcc.exe" only
if they differ) and iterate that list returning the first existing file; update
_find_nvcc to use that de-duplicated probe sequence so
candidate/fallback/fallback_exe logic is removed but behavior remains the same.
tilelang/contrib/msvc.py (2)

216-224: Narrow the blanket except Exception when probing tvm_ffi.libinfo.

Catching Exception hides real bugs (for example a typo in find_windows_implib would silently degrade link behavior). Narrow to the concrete failure modes (ImportError, AttributeError, OSError) and keep the warning.

♻️ Proposed fix
-    try:
-        from tvm_ffi import libinfo as tvm_ffi_libinfo
-
-        add_import_lib(tvm_ffi_libinfo.find_windows_implib())
-    except Exception as e:
-        import logging
-
-        logging.getLogger(__name__).warning("Could not locate tvm_ffi import lib: %s", e)
+    try:
+        from tvm_ffi import libinfo as tvm_ffi_libinfo
+
+        add_import_lib(tvm_ffi_libinfo.find_windows_implib())
+    except (ImportError, AttributeError, OSError) as e:
+        import logging
+
+        logging.getLogger(__name__).warning("Could not locate tvm_ffi import lib: %s", e)

As per static-analysis hint Ruff BLE001 on line 220.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/contrib/msvc.py` around lines 216 - 224, The current try/except
around importing tvm_ffi.libinfo and calling
tvm_ffi_libinfo.find_windows_implib() uses a blanket except Exception which
hides real bugs; change the except to catch only the concrete failure modes
(ImportError, AttributeError, OSError) and keep the existing
logging.getLogger(__name__).warning(...) call so failures still warn but other
unexpected exceptions propagate; reference the import of tvm_ffi, the
tvm_ffi_libinfo.find_windows_implib() call, and the add_import_lib() invocation
when making the change.

152-161: _normalize_option silently passes through unrecognized GCC flags to cl.exe.

Only -I, -D, -g, -fPIC, and -std= are translated or dropped. Unrecognized flags like -L<path>, -l<name>, -Wl,…, -shared, -pthread, -fvisibility=…, -Wno-…, or -O3 will be passed to cl.exe unchanged, causing it to error out or misinterpret them.

Consider extending the mapping for common flags that may be passed by TVM's Windows runtime, or silently dropping any unrecognized flags prefixed with -.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tilelang/contrib/msvc.py` around lines 152 - 161, The _normalize_option
function currently only handles -I, -D, -g, -fPIC, and -std= and will
accidentally pass other GCC-style flags to cl.exe; update _normalize_option to
(1) explicitly handle common flags used on Windows by mapping or dropping them
(e.g. treat options starting with "-L" and "-l" as linker directives to be
dropped here or converted for the linker, drop "-shared", "-pthread",
"-fvisibility=", "-Wl,", "-Wno-", and translate or drop optimization flags like
"-O*"), and (2) as a safe fallback, if an option starts with "-" and is not one
of the handled cases, return None so the flag is not forwarded to cl.exe; keep
the function name _normalize_option and its return semantics.
cmake/FindPipCUDAToolkit.cmake (3)

130-137: Dead branches in arch normalization.

_tilelang_sdk_arch is seeded from _tilelang_target_arch, which is already one of x64 / x86 / arm64 per lines 83-88. The if/elif branches just re-assign the same value; the else fallback to x64 is unreachable given the upstream initialization. Collapsing this to the single initial set() removes the confusion.

♻️ Suggested simplification
-  set(_tilelang_sdk_arch "${_tilelang_target_arch}")
-  if(_tilelang_sdk_arch STREQUAL "arm64")
-    set(_tilelang_sdk_arch "arm64")
-  elseif(_tilelang_sdk_arch STREQUAL "x86")
-    set(_tilelang_sdk_arch "x86")
-  else()
-    set(_tilelang_sdk_arch "x64")
-  endif()
+  set(_tilelang_sdk_arch "${_tilelang_target_arch}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/FindPipCUDAToolkit.cmake` around lines 130 - 137, _tilelang_sdk_arch is
redundantly normalized from _tilelang_target_arch using unreachable branches;
remove the if/elseif/else block and keep a single assignment of
_tilelang_sdk_arch from _tilelang_target_arch (i.e., just set _tilelang_sdk_arch
"${_tilelang_target_arch}"), ensuring no fallback to "x64" remains and
preserving the original variable names _tilelang_sdk_arch and
_tilelang_target_arch.

243-258: Duplicate PATH / PATHEXT prefix handling.

Lines 59-81 already prepend _tilelang_system_path_candidates to ENV{PATH} and install the PATHEXT default, and lines 169-171 already prepend _tilelang_sdk_bin. Re-running the exact same blocks here (244-250, 251-255, 256-258) prepends those entries a second time within the same configure, making ENV{PATH} grow and duplicating PATHEXT handling for no functional reason.

♻️ Suggested change
-  if(_tilelang_system_path_candidates)
-    list(JOIN _tilelang_system_path_candidates ";" _tilelang_system_path_prefix)
-    if(DEFINED ENV{PATH} AND NOT "$ENV{PATH}" STREQUAL "")
-      set(ENV{PATH} "${_tilelang_system_path_prefix};$ENV{PATH}")
-    else()
-      set(ENV{PATH} "${_tilelang_system_path_prefix}")
-    endif()
-  endif()
-  if(NOT DEFINED ENV{PATHEXT} OR "$ENV{PATHEXT}" STREQUAL "")
-    set(ENV{PATHEXT} "${_tilelang_default_pathext}")
-  elseif(NOT "$ENV{PATHEXT}" MATCHES "(^|;)[.]EXE($|;)")
-    set(ENV{PATHEXT} "$ENV{PATHEXT};${_tilelang_default_pathext}")
-  endif()
-  if(NOT _tilelang_sdk_bin STREQUAL "")
-    set(ENV{PATH} "${_tilelang_sdk_bin};$ENV{PATH}")
-  endif()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/FindPipCUDAToolkit.cmake` around lines 243 - 258, This diff duplicates
earlier PATH/PATHEXT prepending logic; remove or guard the repeated block that
re-prepends _tilelang_system_path_candidates and _tilelang_sdk_bin and
re-applies PATHEXT defaults. Locate the second occurrence around the block
manipulating ENV{PATH} and ENV{PATHEXT} (references:
_tilelang_system_path_candidates, _tilelang_default_pathext, _tilelang_sdk_bin)
and either delete this duplicated section or wrap it with a one-time guard flag
(e.g. set a variable like _tilelang_paths_prepended and check NOT
_tilelang_paths_prepended before modifying ENV{PATH}/ENV{PATHEXT}) so
PATH/PATHEXT are only modified once per configure.

464-494: _tilelang_run_find_pip_cuda cannot distinguish the two failure modes of find_pip_cuda.py.

Both "no cu<ver> dir found" and "cu<ver> resolved but nvcc missing" exit with code 1 and no stderr. If the first Python candidate lands in the second bucket, the loop silently tries the next interpreter and the final message(STATUS "FindPipCUDAToolkit: pip-installed CUDA toolkit not found") (line 510) buries the real cause. Pairing this with a short stderr message in find_pip_cuda.py (see my note on its main()) and forwarding OUTPUT_STRIP_TRAILING_WHITESPACE + capturing ERROR_VARIABLE here would make misconfiguration diagnosable without re-running manually.

♻️ Suggested diff
-      execute_process(
-        COMMAND "${_python}" "${CMAKE_CURRENT_LIST_DIR}/find_pip_cuda.py" "${ARGV2}"
-        OUTPUT_VARIABLE _candidate_output
-        OUTPUT_STRIP_TRAILING_WHITESPACE
-        RESULT_VARIABLE _candidate_result
-      )
+      execute_process(
+        COMMAND "${_python}" "${CMAKE_CURRENT_LIST_DIR}/find_pip_cuda.py" "${ARGV2}"
+        OUTPUT_VARIABLE _candidate_output
+        ERROR_VARIABLE _candidate_err
+        OUTPUT_STRIP_TRAILING_WHITESPACE
+        RESULT_VARIABLE _candidate_result
+      )
+      if(NOT _candidate_result EQUAL 0 AND NOT "${_candidate_err}" STREQUAL "")
+        message(STATUS "FindPipCUDAToolkit: ${_python} -> ${_candidate_err}")
+      endif()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/FindPipCUDAToolkit.cmake` around lines 464 - 494, Modify function
_tilelang_run_find_pip_cuda to capture execute_process stderr and surface it
back to the caller: add ERROR_VARIABLE _candidate_error to both execute_process
calls (keep OUTPUT_STRIP_TRAILING_WHITESPACE), and when a candidate returns
non‑zero but _candidate_error is non‑empty, set _result_json to _candidate_error
and _result_python to the current ${_python} and break the loop so the caller
can log the real stderr; keep the existing behavior for successful (RESULT 0)
runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/target/stubs/dynlib.h`:
- Around line 59-95: In dynlib_find_loaded, avoid an out-of-bounds read when
EnumProcessModules reports a larger 'needed' after resizing by computing the
module count from the actual allocated buffer size (buf_bytes) rather than the
reported 'needed'; clamp or recompute DWORD count = (buf_bytes /
sizeof(HMODULE)) (or take min(needed/sizeof(HMODULE),
buf_bytes/sizeof(HMODULE))) before iterating, so the for-loop over modules[i]
cannot exceed the allocated array; ensure this change covers both the initial
and post-realloc paths and still frees modules on all early returns.

---

Outside diff comments:
In `@tilelang/cache/kernel_cache.py`:
- Around line 91-107: The current loop over lib_names returns on the first match
so the stamp only reflects a single library; change the logic in the kernel
cache routine that builds the library stamp (use the existing lib_names,
lib_dirs, sha256 variables) to collect all matching library files instead of
returning immediately, compute each file's SHA-256 (as done with
file_hash.update(...)) and combine their hex digests (e.g., concatenate with a
separator or hash the concatenation) into a single stamp string (replace the
current return f"{name}:{file_hash.hexdigest()}" with a combined digest for all
found libs), and still return None if no libraries are found; ensure the symbol
names lib_names, lib_dirs, sha256, and file_hash.hexdigest() are used to locate
and update the code.

---

Duplicate comments:
In `@cmake/FindPipCUDAToolkit.cmake`:
- Around line 283-285: The linker flag lines in _tilelang_activate_msvc_env
repeatedly prepend and FORCE-cache into CMAKE_EXE_LINKER_FLAGS,
CMAKE_SHARED_LINKER_FLAGS and CMAKE_MODULE_LINKER_FLAGS on every configure,
causing accumulation; add an idempotency guard (e.g. check/set a variable like
_TILELANG_LINKER_FLAGS_APPLIED) around those three set(...) calls so they only
run once (mirroring the existing _TILELANG_INCLUDE_FLAGS_APPLIED pattern used
for include flags) and prevent repeated prepends on subsequent cmake runs.

In `@tilelang/carver/arch/driver/cuda_driver.py`:
- Around line 55-64: The _load_cudart function currently tries a bogus "11" DLL
name and uses ctypes.windll (WinDLL) which is semantically incorrect for the
cdecl CUDA runtime; update _load_cudart to (1) replace the Windows candidate
list with real CUDA runtime names (e.g. "cudart64_130_96.dll",
"cudart64_130.dll", "cudart64_12.dll", "cudart64_110.dll" or similar valid
suffixes) instead of "11", and (2) call ctypes.cdll.LoadLibrary on Windows as
well (use ctypes.cdll.LoadLibrary(f"...") inside the Windows loop) so the
runtime is loaded with cdecl semantics; keep raising OSError on not found so
callers like get_device_attribute that catch exceptions continue to behave as
before.

In `@tilelang/contrib/msvc.py`:
- Line 175: The MD5 usage for generating the filename prefix should be marked
non-security-sensitive: modify the call to hashlib.md5(...) used when computing
the variable prefix so it passes usedforsecurity=False to hashlib.md5 (i.e.,
update the hashlib.md5(...) invocation in the prefix assignment in msvc.py) to
silence Ruff S324 and document intent on FIPS-restricted systems.
- Around line 113-145: The function get_msvc_subprocess_env currently caches and
returns base_env on failures, which masks missing MSVC vars; instead ensure that
on any VsDevCmd discovery/import failure or when cl.exe is not found in the
compiler_env you set _MSVC_ENV_CACHE to None (not base_env) and set
_MSVC_ENV_ERROR accordingly, then return None so callers (e.g., create_shared)
can surface the detailed error; update the branches that currently assign
_MSVC_ENV_CACHE = base_env (the lines handling missing vsdevcmd, import failure,
and missing cl.exe) to assign None and return None while preserving the
_MSVC_ENV_ERROR message.

---

Nitpick comments:
In `@cmake/find_pip_cuda.py`:
- Around line 186-197: The main() currently exits silently on failures; update
it to write explicit error diagnostics to sys.stderr before calling sys.exit(1):
when cu_dir is None (from _find_cu_dir()) print a clear message like "could not
resolve cu<ver> directory" with context, and when nvcc is None (after calling
_find_nvcc(cu_dir)) print a message that the resolved cu_dir exists but bin/nvcc
is missing (include the cu_dir path). Ensure messages are short, go to
sys.stderr so CMake's execute_process will surface them, and keep using
sys.exit(1) after printing.
- Around line 28-41: The _find_nvcc function currently probes the same filename
multiple times because _nvcc_name() already returns platform-specific names;
simplify by building an ordered list of unique filenames to check (start with
_nvcc_name(), then add "nvcc" and "nvcc.exe" only if they differ) and iterate
that list returning the first existing file; update _find_nvcc to use that
de-duplicated probe sequence so candidate/fallback/fallback_exe logic is removed
but behavior remains the same.

In `@cmake/FindPipCUDAToolkit.cmake`:
- Around line 130-137: _tilelang_sdk_arch is redundantly normalized from
_tilelang_target_arch using unreachable branches; remove the if/elseif/else
block and keep a single assignment of _tilelang_sdk_arch from
_tilelang_target_arch (i.e., just set _tilelang_sdk_arch
"${_tilelang_target_arch}"), ensuring no fallback to "x64" remains and
preserving the original variable names _tilelang_sdk_arch and
_tilelang_target_arch.
- Around line 243-258: This diff duplicates earlier PATH/PATHEXT prepending
logic; remove or guard the repeated block that re-prepends
_tilelang_system_path_candidates and _tilelang_sdk_bin and re-applies PATHEXT
defaults. Locate the second occurrence around the block manipulating ENV{PATH}
and ENV{PATHEXT} (references: _tilelang_system_path_candidates,
_tilelang_default_pathext, _tilelang_sdk_bin) and either delete this duplicated
section or wrap it with a one-time guard flag (e.g. set a variable like
_tilelang_paths_prepended and check NOT _tilelang_paths_prepended before
modifying ENV{PATH}/ENV{PATHEXT}) so PATH/PATHEXT are only modified once per
configure.
- Around line 464-494: Modify function _tilelang_run_find_pip_cuda to capture
execute_process stderr and surface it back to the caller: add ERROR_VARIABLE
_candidate_error to both execute_process calls (keep
OUTPUT_STRIP_TRAILING_WHITESPACE), and when a candidate returns non‑zero but
_candidate_error is non‑empty, set _result_json to _candidate_error and
_result_python to the current ${_python} and break the loop so the caller can
log the real stderr; keep the existing behavior for successful (RESULT 0) runs.

In `@src/layout/gemm_layouts.cc`:
- Around line 14-21: The TILELANG_COMPILER_UNREACHABLE macro is being defined
locally in gemm_layouts.cc which duplicates behavior across translation units;
move this definition into a shared internal header (e.g., tl_common.h or
internal/common.h) and replace the local definition with an `#include` of that
header in gemm_layouts.cc and other files that need it. Ensure the macro is
wrapped with include guards or a `#ifndef` TILELANG_COMPILER_UNREACHABLE check in
the shared header to avoid redefinition, and preserve the same platform checks
(__MSC_VER, __GNUC__, __clang__) and fallback semantics when migrating.

In `@src/op/parallel.cc`:
- Around line 85-93: The current two-step guard around
op->annotations.Get(attr::kReducerInfo) correctly avoids MSVC optional
assertions but silently ignores a mismatched payload type; update the inner
branch that attempts reducer_info_ref.value().as<Map<Var, ReducerInfo>>() so
that if the cast fails you emit a diagnostic (e.g., DLOG(WARNING) with the
annotation contents or type) rather than silently skipping it—leave the outer
optional guard in place and still populate p->reducer_info_map_.Set(buffer,
info) when the cast succeeds; alternatively, if you prefer failing fast during
development, replace the silent path with an ICHECK on the inner cast to surface
regressions (this mirrors the writer in
ReducerLayoutAnnotator::VisitStmt_(ForNode) which produces Map<Var,
ReducerInfo>).

In `@tilelang/contrib/msvc.py`:
- Around line 216-224: The current try/except around importing tvm_ffi.libinfo
and calling tvm_ffi_libinfo.find_windows_implib() uses a blanket except
Exception which hides real bugs; change the except to catch only the concrete
failure modes (ImportError, AttributeError, OSError) and keep the existing
logging.getLogger(__name__).warning(...) call so failures still warn but other
unexpected exceptions propagate; reference the import of tvm_ffi, the
tvm_ffi_libinfo.find_windows_implib() call, and the add_import_lib() invocation
when making the change.
- Around line 152-161: The _normalize_option function currently only handles -I,
-D, -g, -fPIC, and -std= and will accidentally pass other GCC-style flags to
cl.exe; update _normalize_option to (1) explicitly handle common flags used on
Windows by mapping or dropping them (e.g. treat options starting with "-L" and
"-l" as linker directives to be dropped here or converted for the linker, drop
"-shared", "-pthread", "-fvisibility=", "-Wl,", "-Wno-", and translate or drop
optimization flags like "-O*"), and (2) as a safe fallback, if an option starts
with "-" and is not one of the handled cases, return None so the flag is not
forwarded to cl.exe; keep the function name _normalize_option and its return
semantics.

In `@tilelang/libinfo.py`:
- Around line 49-56: The for...else is unnecessary because the inner loop
returns on match and never breaks, so simplify control flow by removing the else
and placing the RuntimeError raise after the outer loop; specifically in
tilelang.libinfo (the loop iterating TL_LIBS and lib_names that builds
lib_dll_path), keep the nested iteration and the existing return when
os.path.exists/os.path.isfile succeed, then after both loops complete raise the
RuntimeError with the same message using TL_LIBS and lib_names.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 571ca3d0-455e-4379-9037-2fbd23c761bc

📥 Commits

Reviewing files that changed from the base of the PR and between 452c7d6 and befdc3e.

📒 Files selected for processing (32)
  • .gitattributes
  • .gitignore
  • CMakeLists.txt
  • cmake/FindPipCUDAToolkit.cmake
  • cmake/find_pip_cuda.py
  • cmake/generate_windows_import_lib.py
  • cmake/pypi-z3/FindZ3.cmake
  • docs/get_started/Installation.md
  • pyproject.toml
  • src/layout/gemm_layouts.cc
  • src/op/operator.h
  • src/op/parallel.cc
  • src/runtime/logging.cc
  • src/target/codegen_cuda.cc
  • src/target/codegen_cuda.h
  • src/target/stubs/cuda.cc
  • src/target/stubs/cuda.h
  • src/target/stubs/cudart.cc
  • src/target/stubs/dynlib.h
  • src/target/stubs/nvrtc.cc
  • src/transform/layout_reducer.cc
  • tilelang/__init__.py
  • tilelang/cache/kernel_cache.py
  • tilelang/carver/arch/driver/cuda_driver.py
  • tilelang/contrib/cc.py
  • tilelang/contrib/msvc.py
  • tilelang/contrib/nvcc.py
  • tilelang/env.py
  • tilelang/jit/adapter/libgen.py
  • tilelang/jit/adapter/tvm_ffi.py
  • tilelang/jit/adapter/wrapper.py
  • tilelang/libinfo.py
✅ Files skipped from review due to trivial changes (3)
  • .gitignore
  • .gitattributes
  • CMakeLists.txt
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/target/codegen_cuda.h
  • tilelang/jit/adapter/tvm_ffi.py
  • tilelang/jit/adapter/libgen.py
  • cmake/pypi-z3/FindZ3.cmake
  • src/target/stubs/cuda.h
  • src/runtime/logging.cc
  • tilelang/jit/adapter/wrapper.py
  • src/target/stubs/cudart.cc
  • src/target/stubs/cuda.cc

Comment thread src/target/stubs/dynlib.h
@sepcnt

sepcnt commented Apr 24, 2026

Copy link
Copy Markdown
Contributor Author

At this point, we have enabled support for most operators in both compile and JIT modes on Windows through uv sync --extra nvcc, running in isolation mode without requiring a system-wide CUDA toolkit installation. That said, ROCm support remains broken and needs further ablation studies to isolate the root cause.

@sepcnt
sepcnt force-pushed the windows branch 6 times, most recently from d8b6dd8 to 2f5cede Compare April 24, 2026 19:24
@SiriusNEO

Copy link
Copy Markdown
Collaborator

Yes. Our ROCm CI seems to have some problems, but it's not an issue with this PR.

@sepcnt

sepcnt commented Apr 25, 2026

Copy link
Copy Markdown
Contributor Author

Yes. Our ROCm CI seems to have some problems, but it's not an issue with this PR.

The last available torch version is 2.13.0.dev20260422+rocm7.1 + triton-rocm 3.7.0+gitb4e20bbe, unknown change in 2.13.0.dev20260424+rocm7.1 + triton-rocm 3.7.0+git88b227e2 may break DLTensor layout.

@sepcnt

sepcnt commented Apr 25, 2026

Copy link
Copy Markdown
Contributor Author

Update: ccache is now supported on Windows builds, including when PEP 517 build isolation is enabled. This should significantly speed up incremental rebuilds in isolated environments.

@sepcnt

sepcnt commented Apr 25, 2026

Copy link
Copy Markdown
Contributor Author

Parallel autotune is now available on Windows as well, though it may be worth further investigation into why tvm-ffi works on Unix without the GIL.

@sepcnt
sepcnt force-pushed the windows branch 3 times, most recently from 82d7d73 to baa4400 Compare April 27, 2026 09:57
@sepcnt
sepcnt requested a review from SiriusNEO April 27, 2026 10:41
@sepcnt

sepcnt commented Apr 27, 2026

Copy link
Copy Markdown
Contributor Author

Overall looks good, but we don't have a Windows CI to test it. Maybe we could test it locally? And left some comments.

Overall looks good, but we don't have a Windows CI to test it. Maybe we could test it locally? And left some comments.

All tests passed, with the exception of those related to tmcgen05 or cutlass-dsl.

@SiriusNEO

SiriusNEO commented Apr 28, 2026 •

Copy link
Copy Markdown
Collaborator

No more comments, looks good to me. cc @LeiWang1999. Since this PR is quite large, it may take some time to review and merge.

@SiriusNEO
SiriusNEO self-requested a review April 28, 2026 13:01
SiriusNEO
SiriusNEO previously approved these changes Apr 28, 2026
Comment thread src/op/parallel.cc
@LeiWang1999

Copy link
Copy Markdown
Member

@SiriusNEO I think this pr is ready, would you mind helping resolve the conflict and run perf regression test, then we can let it in.

@sepcnt
sepcnt requested a review from SiriusNEO May 6, 2026 07:33
@SiriusNEO
SiriusNEO merged commit c2cabd9 into tile-ai:main May 6, 2026
12 of 13 checks passed
@sepcnt
sepcnt deleted the windows branch May 7, 2026 01:56
Calaweh pushed a commit to Calaweh/tilelang that referenced this pull request May 20, 2026
…ues (tile-ai#2093)

* [Feature] Add Windows support with cross-platform minimal diff

* [Feature] Add ccache support for Windows

* [Feature] Add parallel autotune support for Windows

* [Feature] Add nvrtc support for Windows

* [Bugfix] Fix tilelibrary support for Windows

* [Bugfix] Fix alignment for Windows

* [Feature] Add clang-cl support for Windows

* [Bugfix] Fix numeric limit for Windows

* [Bugfix] Fix rand for Windows

* [Misc] Use dynamic test workload to avoid hang

* [Bugfix] Fix TMA alignment for Windows and sm120

* [Bugfix] Add unix-like hex formatter for Windows

* [CI] Add Windows wheel job

* [Feature] Flexible CUDA loader

* fix rebase

---------

Co-authored-by: SiriusNEO <chaofan@deepseek.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants