https://github.com/igorban-intel created 
https://github.com/llvm/llvm-project/pull/229710

OpenCL C v3.0 s6.5.1 states that integer division by zero and integer
division whose result lies outside the range of the type (INT_MIN / -1)
do not cause an exception but result in an unspecified value. Neither
is undefined behavior, so -fsanitize=integer-divide-by-zero and the
division check of -fsanitize=signed-integer-overflow must not report
them. Skip the check for integer / and % when the input language is
OpenCL C or C++ for OpenCL, the same way the shift exponent check is
already skipped there.

The specification names only division; % is treated the same way,
since C states the zero-divisor case for both operators and makes
INT_MIN % -1 undefined only because INT_MIN / -1 is.

Signed overflow checks for +, - and * are unaffected.

Assisted-by: Claude Opus 5.5

>From c99f8985630886067d88348f37d839de3ebf9cd5 Mon Sep 17 00:00:00 2001
From: "Gorban, Igor" <[email protected]>
Date: Wed, 7 Oct 2026 11:05:25 +0200
Subject: [PATCH] [clang][OpenCL] Do not emit UBSan divrem checks in OpenCL C

OpenCL C v3.0 s6.5.1 states that integer division by zero and integer
division whose result lies outside the range of the type (INT_MIN / -1)
do not cause an exception but result in an unspecified value. Neither
is undefined behavior, so -fsanitize=integer-divide-by-zero and the
division check of -fsanitize=signed-integer-overflow must not report
them. Skip the check for integer / and % when the input language is
OpenCL C or C++ for OpenCL, the same way the shift exponent check is
already skipped there.

The specification names only division; % is treated the same way,
since C states the zero-divisor case for both operators and makes
INT_MIN % -1 undefined only because INT_MIN / -1 is.

Signed overflow checks for +, - and * are unaffected.

Assisted-by: Claude Opus 5.5
---
 clang/docs/ReleaseNotes.md               |   8 ++
 clang/lib/CodeGen/CGExprScalar.cpp       |   9 +-
 clang/test/CodeGenOpenCL/ubsan-divrem.cl | 140 +++++++++++++++++++++++
 3 files changed, 155 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/CodeGenOpenCL/ubsan-divrem.cl

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 08442f74879af..8991e7ccb7667 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -920,6 +920,14 @@ features cannot lower the translation-unit ABI level;
   ``cl_khr_subgroup_shuffle``, and ``cl_khr_subgroup_shuffle_relative`` are
   promoted to core features in OpenCL C 3.1. A target claiming OpenCL C 3.1
   conformance without supporting one of these features is now diagnosed.
+- ``-fsanitize=integer-divide-by-zero`` and the division check of
+  ``-fsanitize=signed-integer-overflow`` no longer instrument integer ``/`` and
+  ``%`` in OpenCL C and C++ for OpenCL. OpenCL C defines integer division by
+  zero and ``INT_MIN / -1`` to yield an unspecified value without raising an
+  exception (OpenCL C v3.0 s6.5.1). The specification names only division;
+  ``%`` is treated the same way, since C states the zero-divisor case for both
+  operators (C99 6.5.5p5) and makes ``INT_MIN % -1`` undefined only because
+  ``INT_MIN / -1`` is (C11 6.5.5p6).
 
 ### Target Specific Changes
 
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp 
b/clang/lib/CodeGen/CGExprScalar.cpp
index 7ecaa149f7b34..a4ceee62dfc7c 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -4320,7 +4320,10 @@ Value *ScalarExprEmitter::EmitDiv(const BinOpInfo &Ops) {
                                      SanitizerKind::SO_SignedIntegerOverflow,
                                      SanitizerKind::SO_FloatDivideByZero},
                                     SanitizerHandler::DivremOverflow);
-    if ((CGF.SanOpts.has(SanitizerKind::IntegerDivideByZero) ||
+    // OpenCL C v3.0 s6.5.1: integer division by zero and INT_MIN / -1 yield an
+    // unspecified value, not undefined behavior.
+    if (!CGF.getLangOpts().OpenCL &&
+        (CGF.SanOpts.has(SanitizerKind::IntegerDivideByZero) ||
          CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow)) &&
         Ops.Ty->isIntegerType() &&
         (Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow())) {
@@ -4369,7 +4372,9 @@ Value *ScalarExprEmitter::EmitDiv(const BinOpInfo &Ops) {
 
 Value *ScalarExprEmitter::EmitRem(const BinOpInfo &Ops) {
   // Rem in C can't be a floating point type: C99 6.5.5p2.
-  if ((CGF.SanOpts.has(SanitizerKind::IntegerDivideByZero) ||
+  // OpenCL: treated like integer division, see EmitDiv.
+  if (!CGF.getLangOpts().OpenCL &&
+      (CGF.SanOpts.has(SanitizerKind::IntegerDivideByZero) ||
        CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow)) &&
       Ops.Ty->isIntegerType() &&
       (Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow())) {
diff --git a/clang/test/CodeGenOpenCL/ubsan-divrem.cl 
b/clang/test/CodeGenOpenCL/ubsan-divrem.cl
new file mode 100644
index 0000000000000..cab30708e51e9
--- /dev/null
+++ b/clang/test/CodeGenOpenCL/ubsan-divrem.cl
@@ -0,0 +1,140 @@
+// OpenCL C v3.0 s6.5.1 defines both conditions that
+// __ubsan_handle_divrem_overflow diagnoses: integer division whose result lies
+// outside the range of the integer type -- INT_MIN / -1 -- and integer divide
+// by zero do not cause an exception but result in an unspecified value. Clang
+// therefore does not emit the check in OpenCL C or C++ for OpenCL, the same 
way
+// it does not emit -fsanitize=shift-exponent there.
+//
+// Overflow of +, - and * is not covered by that wording, so the arithmetic
+// controls must remain instrumented whenever signed-integer-overflow is
+// enabled.
+//
+// The KERNEL/GLOBAL macros come from the command line so that the same source
+// can be compiled as OpenCL C, as C++ for OpenCL, as C and as SYCL device 
code.
+// Every run passes -disable-llvm-passes so the checks see the frontend's own
+// output rather than the output of the default OpenCL -O2 pipeline.
+
+// OpenCL C omits divrem checks while retaining arithmetic checks.
+// RUN: %clang_cc1 %s -triple spir64-unknown-unknown -emit-llvm -o - \
+// RUN:   -disable-llvm-passes -DKERNEL=__kernel -DGLOBAL=__global \
+// RUN:   -fsanitize=integer-divide-by-zero,signed-integer-overflow \
+// RUN:   -fsanitize-minimal-runtime \
+// RUN:   | FileCheck %s --check-prefixes=CHECK,ARITH \
+// RUN:       --implicit-check-not=__ubsan_handle_divrem_overflow
+//
+// C++ for OpenCL omits divrem checks while retaining arithmetic checks.
+// RUN: %clang_cc1 %s -triple spir64-unknown-unknown -cl-std=clc++2021 \
+// RUN:   -emit-llvm -disable-llvm-passes -o - \
+// RUN:   -DKERNEL=__kernel -DGLOBAL=__global \
+// RUN:   -fsanitize=integer-divide-by-zero,signed-integer-overflow \
+// RUN:   -fsanitize-minimal-runtime \
+// RUN:   | FileCheck %s --check-prefixes=CHECK,ARITH \
+// RUN:       --implicit-check-not=__ubsan_handle_divrem_overflow
+//
+// OpenCL C omits integer-divide-by-zero checks.
+// RUN: %clang_cc1 %s -triple spir64-unknown-unknown -emit-llvm -o - \
+// RUN:   -disable-llvm-passes -DKERNEL=__kernel -DGLOBAL=__global \
+// RUN:   -fsanitize=integer-divide-by-zero -fsanitize-minimal-runtime \
+// RUN:   | FileCheck %s --check-prefix=CHECK \
+// RUN:       --implicit-check-not=__ubsan_handle_divrem_overflow
+//
+// OpenCL C omits signed-overflow divrem checks but retains arithmetic checks.
+// RUN: %clang_cc1 %s -triple spir64-unknown-unknown -emit-llvm -o - \
+// RUN:   -disable-llvm-passes -DKERNEL=__kernel -DGLOBAL=__global \
+// RUN:   -fsanitize=signed-integer-overflow -fsanitize-minimal-runtime \
+// RUN:   | FileCheck %s --check-prefixes=CHECK,ARITH \
+// RUN:       --implicit-check-not=__ubsan_handle_divrem_overflow
+//
+// C retains both divrem and arithmetic checks.
+// RUN: %clang_cc1 %s -x c -triple x86_64-unknown-linux-gnu -emit-llvm -o - \
+// RUN:   -disable-llvm-passes -DKERNEL= -DGLOBAL= \
+// RUN:   -fsanitize=integer-divide-by-zero,signed-integer-overflow \
+// RUN:   -fsanitize-minimal-runtime \
+// RUN:   | FileCheck %s --check-prefixes=CHECK,ARITH,SIGNED,UNSIGNED
+//
+// SYCL device C++ retains both divrem and arithmetic checks.
+// RUN: %clang_cc1 %s -x c++ -fsycl-is-device -triple spir64-unknown-unknown \
+// RUN:   -emit-llvm -disable-llvm-passes -o - \
+// RUN:   -DKERNEL='[[clang::sycl_external]]' -DGLOBAL= \
+// RUN:   -fsanitize=integer-divide-by-zero,signed-integer-overflow \
+// RUN:   -fsanitize-minimal-runtime \
+// RUN:   | FileCheck %s --check-prefixes=CHECK,ARITH,SIGNED,UNSIGNED
+//
+// C retains integer-divide-by-zero checks.
+// RUN: %clang_cc1 %s -x c -triple x86_64-unknown-linux-gnu -emit-llvm -o - \
+// RUN:   -disable-llvm-passes -DKERNEL= -DGLOBAL= \
+// RUN:   -fsanitize=integer-divide-by-zero -fsanitize-minimal-runtime \
+// RUN:   | FileCheck %s --check-prefixes=CHECK,SIGNED,UNSIGNED
+//
+// C retains signed-overflow divrem and arithmetic checks.
+// RUN: %clang_cc1 %s -x c -triple x86_64-unknown-linux-gnu -emit-llvm -o - \
+// RUN:   -disable-llvm-passes -DKERNEL= -DGLOBAL= \
+// RUN:   -fsanitize=signed-integer-overflow -fsanitize-minimal-runtime \
+// RUN:   | FileCheck %s --check-prefixes=CHECK,ARITH,SIGNED
+
+// Signed: both the divide-by-zero and the INT_MIN / -1 arm would apply.
+// CHECK-LABEL: divrem_signed
+// SIGNED: call{{.*}} @__ubsan_handle_divrem_overflow
+// CHECK: sdiv i32
+// SIGNED: call{{.*}} @__ubsan_handle_divrem_overflow
+// CHECK: srem i32
+KERNEL void divrem_signed(GLOBAL int *a, GLOBAL int *b, GLOBAL int *out) {
+  out[0] = a[0] / b[0];
+  out[1] = a[0] % b[0];
+}
+
+// Unsigned: only the divide-by-zero arm would apply.
+// CHECK-LABEL: divrem_unsigned
+// UNSIGNED: call{{.*}} @__ubsan_handle_divrem_overflow
+// CHECK: udiv i32
+// UNSIGNED: call{{.*}} @__ubsan_handle_divrem_overflow
+// CHECK: urem i32
+KERNEL void divrem_unsigned(GLOBAL unsigned int *a, GLOBAL unsigned int *b,
+                            GLOBAL unsigned int *out) {
+  out[0] = a[0] / b[0];
+  out[1] = a[0] % b[0];
+}
+
+// Compound assignment reaches the same emission path. This is the form the
+// OpenCL-CTS integer_divideAssign and integer_moduloAssign kernels use.
+// CHECK-LABEL: compound_divrem
+// SIGNED: call{{.*}} @__ubsan_handle_divrem_overflow
+// CHECK: sdiv i32
+// SIGNED: call{{.*}} @__ubsan_handle_divrem_overflow
+// CHECK: srem i32
+KERNEL void compound_divrem(GLOBAL int *a, GLOBAL int *out) {
+  out[0] /= a[0];
+  out[1] %= a[0];
+}
+
+// Integer vectors are not instrumented today: the check requires a scalar
+// integer type. The suppressed runs still must not find a handler here, so a
+// later change that instruments vectors outside the guarded path fails this
+// test. The OpenCL-CTS integer_ops kernels run every vector width.
+// CHECK-LABEL: vector_divrem
+// CHECK: sdiv <4 x i32>
+// CHECK: srem <4 x i32>
+typedef int vint4 __attribute__((ext_vector_type(4)));
+KERNEL void vector_divrem(GLOBAL vint4 *a, GLOBAL vint4 *b, GLOBAL vint4 *out) 
{
+  out[0] = a[0] / b[0];
+  out[1] = a[0] % b[0];
+}
+
+// Controls: signed overflow in +, - and * must still be checked.
+// CHECK-LABEL: add_still_checked
+// ARITH: call{{.*}} @__ubsan_handle_add_overflow
+KERNEL void add_still_checked(GLOBAL int *a, GLOBAL int *b, GLOBAL int *out) {
+  out[0] = a[0] + b[0];
+}
+
+// CHECK-LABEL: sub_still_checked
+// ARITH: call{{.*}} @__ubsan_handle_sub_overflow
+KERNEL void sub_still_checked(GLOBAL int *a, GLOBAL int *b, GLOBAL int *out) {
+  out[0] = a[0] - b[0];
+}
+
+// CHECK-LABEL: mul_still_checked
+// ARITH: call{{.*}} @__ubsan_handle_mul_overflow
+KERNEL void mul_still_checked(GLOBAL int *a, GLOBAL int *b, GLOBAL int *out) {
+  out[0] = a[0] * b[0];
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to