Skip to content

Commit d3702bd

Browse files
timthluTim Lu
andauthored
Enable LLVM AddressSanitizer for triton-shared (#294)
Implemented support for debugging Triton programs with sanitizers: - New MLIR pass added to triton-shared-opt: --add-debug-info, appending missing #di attributes for useful error logs - New LLVM pass ran using opt: --sanitizer-attributes, appending missing sanitizer attributes to functions in LLVM IR - Adding -fsanitize flags during compilation + linking. Doing so required changing llc and g++ to clang++ - Introducing environment variable SANITIZER_TYPE to enable the sanitizers during JIT compilation - 3 scripts to facilitate simple build, runtime setup, and running a Triton program with sanitizers enabled (see usage) Usage for anyone who wants to debug a Triton program they have written: 1. /triton_shared/scripts/build_triton_shared_for_sanitizers.sh: one time build script. Sets up a virtual environment, installs a custom LLVM with compiler-rt, openmp, clang, and mlir. Then builds triton_shared using this custom LLVM 2. source /triton_shared/scripts/setup_runtime_for_sanitizers.sh: after installation, prepare the runtime once to activate the virtual environment and set environment variables. Should be called with source when a new terminal is used 3. /triton_shared/scripts/run_triton.sh [asan|tsan] python triton_program.py: executes the Triton program with ASan or TSan enabled, with the required environment variables --------- Co-authored-by: Tim Lu <t-timlu@microsoft.com>
1 parent 1f0ef20 commit d3702bd

27 files changed

Lines changed: 890 additions & 10 deletions

backend/compiler.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import shutil
1111
import subprocess
1212
import functools
13+
import triton
1314
from pathlib import Path
1415

1516
def _get_triton_shared_opt_path() -> str:
@@ -33,6 +34,16 @@ def _dump_ir_if_needed(files):
3334
for f in files:
3435
shutil.copy(f, os.path.join(path, os.path.basename(f)))
3536

37+
def _get_sanitizer_type():
38+
# returns "" if not set
39+
# throws error if set to something other than "asan"
40+
sanitizer_type = os.getenv("TRITON_SHARED_SANITIZER_TYPE", "")
41+
42+
if sanitizer_type != "" and sanitizer_type != "asan":
43+
# throw error
44+
raise Exception(f"TRITON_SHARED_SANITIZER_TYPE {sanitizer_type} is invalid.")
45+
46+
return sanitizer_type
3647

3748
def _ttir_to_ttsharedir(mod):
3849
# Get Triton-MLIR as string
@@ -43,7 +54,16 @@ def _ttir_to_ttsharedir(mod):
4354
Path(src_path).write_text(ttir_code)
4455
_dump_ir_if_needed([src_path])
4556
triton_shared_opt_path = _get_triton_shared_opt_path()
46-
subprocess.check_call([triton_shared_opt_path, src_path, "--triton-to-linalg-experimental", "--mlir-print-debuginfo", "-o", dst_path])
57+
58+
subprocess_args = [triton_shared_opt_path, src_path, "--triton-to-linalg-experimental", "--mlir-print-debuginfo", "-o", dst_path]
59+
60+
if _get_sanitizer_type() != "":
61+
print("Building with sanitizer support...")
62+
63+
# has to run before the other passes as operates on the tt dialect
64+
subprocess_args.insert(2, "--add-llvm-debug-info")
65+
66+
subprocess.check_call(subprocess_args)
4767
return Path(dst_path).read_text()
4868

4969

@@ -118,8 +138,38 @@ def _llir_to_bin(llir: str, metadata):
118138
src_path = os.path.join(tmpdir, "kernel.ll")
119139
dst_path = os.path.join(tmpdir, "kernel.o")
120140
Path(src_path).write_text(llir)
121-
llc_path = _get_llvm_bin_path("llc")
122-
subprocess.check_call([llc_path, src_path, "-filetype=obj", "-o", dst_path])
141+
142+
sanitizer_type = _get_sanitizer_type()
143+
144+
if sanitizer_type != "":
145+
# using a sanitizer
146+
# invoke pass to append sanitizer attributes
147+
instrumented_src_path = os.path.join(tmpdir, "kernel-instrumented.ll")
148+
149+
opt_path = _get_llvm_bin_path("opt")
150+
top_level_triton_path = os.path.dirname(triton.__file__)
151+
sanitizer_attributes_pass_path = str(next(Path(top_level_triton_path).rglob("libSanitizerAttributes.so"), None))
152+
153+
if not sanitizer_attributes_pass_path:
154+
raise Exception(f"libSanitizerAttributes.so does not exist.")
155+
156+
subprocess.check_call([opt_path, "-load-pass-plugin", sanitizer_attributes_pass_path,
157+
"-passes=sanitizer-attributes", f"-sanitizer-type={sanitizer_type}", "-S", src_path,
158+
"-o", instrumented_src_path])
159+
160+
# compile to object file
161+
clang_path = _get_llvm_bin_path("clang++")
162+
163+
subprocess_args = [clang_path, "-c", instrumented_src_path, "-o", dst_path]
164+
165+
if sanitizer_type == "asan":
166+
subprocess_args.extend(["-g", "-fsanitize=address", "-mllvm", "-asan-stack=0"])
167+
168+
subprocess.check_call(subprocess_args)
169+
else:
170+
llc_path = _get_llvm_bin_path("llc")
171+
subprocess.check_call([llc_path, src_path, "-filetype=obj", "-o", dst_path])
172+
123173
return Path(dst_path).read_bytes()
124174

125175

backend/driver.py

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,31 @@
1212
from triton.backends.driver import DriverBase
1313
from triton.backends.compiler import GPUTarget
1414

15+
def _get_llvm_bin_path(bin_name: str) -> str:
16+
path = os.getenv("LLVM_BINARY_DIR", "")
17+
if path == "":
18+
raise Exception("LLVM_BINARY_DIR is not set.")
19+
return os.path.join(path, bin_name)
20+
21+
def _get_sanitizer_type():
22+
# returns "" if not set
23+
# throws error if set to something other than "asan"
24+
sanitizer_type = os.getenv("TRITON_SHARED_SANITIZER_TYPE", "")
25+
26+
if sanitizer_type != "" and sanitizer_type != "asan":
27+
# throw error
28+
raise Exception(f"TRITON_SHARED_SANITIZER_TYPE {sanitizer_type} is invalid.")
29+
30+
return sanitizer_type
31+
32+
def _sanitizer_available(sanitizer_type):
33+
if "LD_PRELOAD" not in os.environ:
34+
return False
35+
if f"libclang_rt.{sanitizer_type}.so" not in os.environ["LD_PRELOAD"]:
36+
return False
37+
38+
return True
39+
1540
# -------------------- Launcher ----------------------------
1641
def _ty_to_cpp(ty):
1742
if ty[0] == '*':
@@ -253,7 +278,12 @@ def launch(
253278

254279
if cache_path is None:
255280
with tempfile.TemporaryDirectory() as tmpdir:
281+
sanitizer_type = _get_sanitizer_type()
282+
256283
if platform.system() == "Windows":
284+
if sanitizer_type != "":
285+
raise Exception("Sanitizers are not supported on Windows with triton-shared.")
286+
257287
obj_path = os.path.join(tmpdir, "kernel.obj")
258288
launcher_src_path = os.path.join(tmpdir, "main.cxx")
259289
so_path = os.path.join(tmpdir, "kernel.pyd")
@@ -271,12 +301,30 @@ def launch(
271301
so_path = os.path.join(tmpdir, "kernel.so")
272302
Path(obj_path).write_bytes(kernel_obj)
273303
Path(launcher_src_path).write_text(src)
304+
274305
# Compile it together.
275-
subprocess.check_call([
276-
"g++", "-std=c++17", launcher_src_path, obj_path,
277-
f"-I{py_include_dir}", f"-I{include_dir}", f"-L{py_lib_dir}",
278-
"-shared", f"-l{py_lib}", "-fPIC", "-o", so_path
279-
])
306+
if sanitizer_type != "":
307+
clang_path = _get_llvm_bin_path("clang++")
308+
309+
subprocess_args = [
310+
clang_path, "-std=c++17", launcher_src_path, obj_path,
311+
f"-I{py_include_dir}", f"-I{include_dir}", f"-L{py_lib_dir}",
312+
"-shared", f"-l{py_lib}", "-fPIC", "-o", so_path
313+
]
314+
315+
if not _sanitizer_available(sanitizer_type):
316+
raise Exception(f"Use LD_PRELOAD=\"path/to/libclang_rt.{sanitizer_type}.so\" TRITON_SHARED_SANITIZER_TYPE={sanitizer_type} python ...")
317+
318+
if sanitizer_type == "asan":
319+
subprocess_args.extend(["-g", "-fsanitize=address", "-mllvm", "-asan-stack=0"])
320+
321+
subprocess.check_call(subprocess_args)
322+
else:
323+
subprocess.check_call([
324+
"g++", "-std=c++17", launcher_src_path, obj_path,
325+
f"-I{py_include_dir}", f"-I{include_dir}", f"-L{py_lib_dir}",
326+
"-shared", f"-l{py_lib}", "-fPIC", "-o", so_path
327+
])
280328

281329
with open(so_path, "rb") as f:
282330
cache_path = cache.put(f.read(), filename, binary=True)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
add_subdirectory(Conversion)
22
add_subdirectory(Dialect)
3+
add_subdirectory(Transform)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Copyright (c) Microsoft Corporation.
4+
// Licensed under the MIT license.
5+
//
6+
//===----------------------------------------------------------------------===//
7+
8+
#ifndef TRITON_TRANSFORM_ADDLLVMDEBUGINFO_ADDLLVMDEBUGINFO_H
9+
#define TRITON_TRANSFORM_ADDLLVMDEBUGINFO_ADDLLVMDEBUGINFO_H
10+
11+
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
12+
#include "mlir/Dialect/Linalg/IR/Linalg.h"
13+
#include "mlir/Pass/Pass.h"
14+
#include "mlir/Transforms/DialectConversion.h"
15+
16+
#include "triton/Dialect/Triton/IR/Dialect.h"
17+
18+
namespace mlir {
19+
namespace triton {
20+
21+
std::unique_ptr<OperationPass<ModuleOp>> createAddLLVMDebugInfoPass();
22+
23+
24+
} // namespace triton
25+
} // namespace mlir
26+
27+
#endif // TRITON_TRANSFORM_ADDLLVMDEBUGINFO_ADDLLVMDEBUGINFO_H
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#===------------------------------------------------------------------------===#
2+
#
3+
# Copyright (c) Triton Project Contributors.
4+
#
5+
#===------------------------------------------------------------------------===#
6+
7+
set(LLVM_TARGET_DEFINITIONS Passes.td)
8+
mlir_tablegen(Passes.h.inc -gen-pass-decls --name AddLLVMDebugInfo)
9+
add_public_tablegen_target(AddLLVMDebugInfoTransformPassIncGen)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Copyright (c) Microsoft Corporation.
4+
// Licensed under the MIT license.
5+
//
6+
//===----------------------------------------------------------------------===//
7+
8+
#ifndef ADD_LLVM_DEBUG_INFO_TRANSFORM_PASSES_H
9+
#define ADD_LLVM_DEBUG_INFO_TRANSFORM_PASSES_H
10+
11+
#include "triton-shared/Transform/AddLLVMDebugInfo/AddLLVMDebugInfo.h"
12+
13+
namespace mlir {
14+
namespace triton {
15+
16+
#define GEN_PASS_REGISTRATION
17+
#include "triton-shared/Transform/AddLLVMDebugInfo/Passes.h.inc"
18+
19+
} // namespace triton
20+
} // namespace mlir
21+
22+
#endif
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Copyright (c) Microsoft Corporation.
4+
// Licensed under the MIT license.
5+
//
6+
//===----------------------------------------------------------------------===//
7+
8+
#ifndef ADD_LLVM_DEBUG_INFO_TRANSFORM_PASSES
9+
#define ADD_LLVM_DEBUG_INFO_TRANSFORM_PASSES
10+
11+
include "mlir/Pass/PassBase.td"
12+
13+
def AddLLVMDebugInfo : Pass<"add-llvm-debug-info", "mlir::ModuleOp"> {
14+
let summary = "Add LLVM debug info";
15+
let constructor = "triton::createAddLLVMDebugInfoPass()";
16+
}
17+
18+
#endif
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
add_subdirectory(AddLLVMDebugInfo)

lib/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ add_subdirectory(Analysis)
22
add_subdirectory(AnalysisStructured)
33
add_subdirectory(Conversion)
44
add_subdirectory(Dialect)
5+
add_subdirectory(Sanitizer)
6+
add_subdirectory(Transform)
57
add_subdirectory(Utils)

lib/Sanitizer/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
add_subdirectory(SanitizerAttributes)

0 commit comments

Comments
 (0)