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
