[PATCH v4 36/44] testsuite, sycl: add SYCL support

Markus Metzger <[email protected]>
Newsgroups gmane.comp.gdb.patches
Message-ID <[email protected]>
From: Tankut Baris Aktemur <[email protected]>

Add lib files, board file, and basic test scenarios for SYCL.  The
scenarios test the ability to define and hit breakpoints inside and
outside a SYCL kernel.  The test cases are skipped if it is found out
that the compiler does not support SYCL.

Here is a sample command to run all SYCL tests on the CPU target
device using the Intel® oneAPI DPC++/C++ Compiler:

  $ make check TESTS="gdb.sycl/*.exp" \
    RUNTESTFLAGS="OFFLOAD_DEVICE_GROUP=cpu CXX_FOR_TARGET=icpx"

Alternatively, the intel-offload board file can be used.  This can be
more practical to pick the right compilers.

  $ make check TESTS="gdb.sycl/*.exp" \
    RUNTESTFLAGS="OFFLOAD_DEVICE_GROUP=cpu --target_board='intel-offload'"

Running the GPU tests requires that a gdbserver executable built for
the intelgt target exists in the path and its name is
'gdbserver-intelgt'.

Co-authored-by: Abdul Basit Ijaz <[email protected]>
Co-authored-by: Stephan Rohr <[email protected]>
Co-authored-by: Markus Metzger <[email protected]>
---
 gdb/testsuite/README                   |   9 +
 gdb/testsuite/boards/intel-offload.exp |  36 +++
 gdb/testsuite/gdb.sycl/break.cpp       | 127 ++++++++
 gdb/testsuite/gdb.sycl/break.exp       | 246 +++++++++++++++
 gdb/testsuite/lib/gdb.exp              |  17 +-
 gdb/testsuite/lib/intelgt-utils.exp    |  43 +++
 gdb/testsuite/lib/sycl-devices.cpp     | 106 +++++++
 gdb/testsuite/lib/sycl-hello.cpp       |  33 ++
 gdb/testsuite/lib/sycl-util.cpp        | 135 +++++++++
 gdb/testsuite/lib/sycl.exp             | 399 +++++++++++++++++++++++++
 10 files changed, 1150 insertions(+), 1 deletion(-)
 create mode 100644 gdb/testsuite/boards/intel-offload.exp
 create mode 100644 gdb/testsuite/gdb.sycl/break.cpp
 create mode 100644 gdb/testsuite/gdb.sycl/break.exp
 create mode 100644 gdb/testsuite/lib/intelgt-utils.exp
 create mode 100644 gdb/testsuite/lib/sycl-devices.cpp
 create mode 100644 gdb/testsuite/lib/sycl-hello.cpp
 create mode 100644 gdb/testsuite/lib/sycl-util.cpp
 create mode 100644 gdb/testsuite/lib/sycl.exp

diff --git a/gdb/testsuite/README b/gdb/testsuite/README
index f354144b872..9a962db2835 100644
--- a/gdb/testsuite/README
+++ b/gdb/testsuite/README
@@ -354,6 +354,15 @@ by switching to a different user on the same machine.  These users
 will have random files copied into their $HOME directories, so it is a
 good idea to setup new users just for this purpose.
 
+OFFLOAD_DEVICE_GROUP
+
+This option can be used to restrict the offloading tests to run only
+on a specific group/family of devices.  Multiple target device groups
+can be selected by passing a comma separated list.  By default, it is
+set to "cpu,gpu,accelerator".  Example use:
+
+	make check RUNTESTFLAGS='OFFLOAD_DEVICE_GROUP="gpu,cpu"'
+
 Testing All Simple Boards
 *************************
 
diff --git a/gdb/testsuite/boards/intel-offload.exp b/gdb/testsuite/boards/intel-offload.exp
new file mode 100644
index 00000000000..b5d92ce2f31
--- /dev/null
+++ b/gdb/testsuite/boards/intel-offload.exp
@@ -0,0 +1,36 @@
+# Copyright 2022-2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is a dejagnu "board file" and is used for running the
+# SYCL testsuite.
+#
+# Example usage:
+# bash$ make check TESTS="gdb.sycl/*.exp" RUNTESTFLAGS='--target_board=intel-offload'
+
+load_generic_config "unix"
+process_multilib_options ""
+load_board_description "local-board"
+
+set gdb_test_timeout 100
+
+unset_board_info isremote
+set_board_info isremote 0
+
+set_board_info compiler    "icx"
+set_board_info c++compiler "icpx"
+set_board_info f90compiler "ifx"
+
+puts "Info: Using timeout value $gdb_test_timeout"
+puts "Info: Using C++ compiler icpx and Fortran compiler ifx"
diff --git a/gdb/testsuite/gdb.sycl/break.cpp b/gdb/testsuite/gdb.sycl/break.cpp
new file mode 100644
index 00000000000..e7be926180d
--- /dev/null
+++ b/gdb/testsuite/gdb.sycl/break.cpp
@@ -0,0 +1,127 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2026 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <sycl/sycl.hpp>
+#include "sycl-util.cpp"
+
+static int
+foo ()
+{ /* foo.entry */
+  int a = 0; /* foo.entry */
+  int b = 1; /* foo.1 */
+  return a + b;
+}
+
+static int
+bar ()
+{ /* bar.entry */
+  int a = 0; /* bar.entry */
+  int b = 1; /* bar.1 */
+  return a + b;
+}
+
+struct SingleTask
+{
+  /* Test that we're not leaking host breakpoints into the kernel.  */
+  SingleTask ()
+    {
+      int a = 0; /* host.1 */
+    }
+
+  void operator () () const /* kernel.single_task */
+    { /* kernel.single_task */
+      int a = 0; /* kernel.single_task */
+      int b = 0; /* kernel.1 */
+      int c = foo ();
+      int d = 0; /* kernel.2 */
+      int e = bar ();
+    }
+
+  /* Test that we're not leaking kernel breakpoints into the host.  */
+  ~SingleTask ()
+    {
+    }
+};
+
+struct ParallelFor
+{
+  /* Test that we're not leaking host breakpoints into the kernel.  */
+  ParallelFor ()
+    {
+      int a = 0; /* host.2 */
+    }
+
+  void operator () (sycl::nd_item<1> item) const /* kernel.parallel_for */
+    { /* kernel.parallel_for */
+      sycl::id<1> id = item.get_global_id (); /* kernel.parallel_for */
+      int gid = id.get (0);
+
+      int a = 0; /* kernel.3 */
+      int b = 0; /* kernel.4 */
+      int c = 0; /* kernel.5 */
+      int d = 0; /* kernel.6 */
+    }
+
+  /* Test that we're not leaking kernel breakpoints into the host.  */
+  ~ParallelFor ()
+    {
+    }
+};
+
+int
+main (int argc, char *argv[])
+{
+  sycl::queue queue {get_sycl_queue (argc, argv)};
+
+  /* Submit a dummy kernel to trigger attaching.
+
+     This is a bit awkward.  To start a test, sycl_start continues to to
+     zeModuleCreate inside the user-mode driver to attach to the graphics
+     device.  To cover leaking breakpoints into and out of kernels, we
+     need to be attached before we hit the breakpoint at the SingleTask
+     ctor.
+
+     This will go away with support for automatically attaching.  */
+  queue.single_task ([] () {});
+
+  SingleTask single_task;
+  for (int i = 0; i < 2; i++)
+    {
+      queue.single_task (single_task);
+      queue.wait ();
+    }
+
+  /* Test that breakpoints do not leak into or out of lambda kernels.  */
+  int a = 0; /* host.3 */
+  queue.single_task ([] ()
+    {
+      int a = 0;
+      int b = 0; /* lambda.1 */
+    });
+  queue.wait ();
+
+  ParallelFor parallel_for;
+  sycl::nd_range<1> range {2, 1};
+  for (int i = 0; i < 2; i++)
+    {
+      queue.parallel_for (range, parallel_for);
+      queue.wait ();
+      int a = 0; /* host.4 */
+    }
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.sycl/break.exp b/gdb/testsuite/gdb.sycl/break.exp
new file mode 100644
index 00000000000..f471fa62642
--- /dev/null
+++ b/gdb/testsuite/gdb.sycl/break.exp
@@ -0,0 +1,246 @@
+# Copyright 2026 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Test defining and hitting breakpoints.
+
+load_lib sycl.exp
+
+standard_testfile .cpp
+
+set sycl_device_list [init_sycl_devices_list]
+if {[llength $sycl_device_list] == 0} {
+    unsupported "target does not support SYCL"
+    return
+}
+
+if {[build_executable "failed to compile $srcfile" "$binfile" "$srcfile" \
+	 {sycl debug}]} {
+    return
+}
+
+# Returns the symbol of the current kernel or "".
+proc find_kernel_symbol_gpu {} {
+    set symbol ""
+
+    # The kernel is at the top of the stack - at least on GPUs.
+    gdb_test_multiple "up" "find top frame" {
+	-re -wrap "Initial frame selected; you cannot go up." {
+	    gdb_test_multiple "with print demangle off -- info symbol \$pc" \
+		"find kernel symbol" {
+		    -re -wrap "(\[A-Za-z0-9_\]+) \[^\r\n\]*in section .*" {
+			set symbol $expect_out(1,string)
+			pass $gdb_test_name
+		    }
+		    -re -wrap "" {
+			fail $gdb_test_name
+		    }
+		}
+	}
+	-re -wrap "#\[0-9\]+ .*" {
+	    send_gdb "up\n"
+	    exp_continue
+	}
+    }
+
+    return $symbol
+}
+
+foreach device $sycl_device_list {
+    sycl_with_intelgt_lock $device {
+	clean_restart $testfile
+
+	if {![sycl_start $device]} {
+	    continue
+	}
+
+	set foo_1 [gdb_get_line_number "foo.1"]
+	set bar_1 [gdb_get_line_number "bar.1"]
+	set host_1 [gdb_get_line_number "host.1"]
+	set host_2 [gdb_get_line_number "host.2"]
+	set host_3 [gdb_get_line_number "host.3"]
+	set host_4 [gdb_get_line_number "host.4"]
+	set lambda_1 [gdb_get_line_number "lambda.1"]
+	set kernel_1 [gdb_get_line_number "kernel.1"]
+	set kernel_2 [gdb_get_line_number "kernel.2"]
+	set kernel_3 [gdb_get_line_number "kernel.3"]
+	set kernel_4 [gdb_get_line_number "kernel.4"]
+	set kernel_5 [gdb_get_line_number "kernel.5"]
+	set kernel_6 [gdb_get_line_number "kernel.6"]
+
+	# The user-defined kernel function is wrapped by an actual kernel
+	# provided by the compiler when offloading to a device, so the
+	# user-defined kernel is actually a device function that is called
+	# from or inlined into the actual kernel.
+	#
+	# To test breaking on the kernel and sliding the breakpoint across
+	# the kernel prologue with its potentially multiple entries, we
+	# run each kernel twice.
+	#
+	#   - in the first run, we break at the user-defined kernel
+	#     function symbol and determine the actual kernel symbol.
+	#
+	#   - in the second run, we break at the actual kernel symbol and
+	#     run the rest of the breakpoint tests for this kernel.
+	#
+	# When running on CPU, the kernel function is not actually
+	# offloaded but called by the runtime.  We simply use the same
+	# user kernel function symbol again in this case.
+	#
+	# We're going to set breakpoints
+	#
+	#   - from the host process into kernels
+	#   - from kernels into the host process
+	#   - from kernels into other kernels
+	gdb_breakpoint "SingleTask::operator ()" -message -temporary \
+	    -allow-pending
+	gdb_breakpoint "ParallelFor::operator ()" -message -temporary \
+	    -allow-pending
+
+	with_test_prefix "single_task" {
+	    # We also cover breakpoints leaking into and out of kernels,
+	    # so we start with a host breakpoint located above the kernel.
+	    gdb_breakpoint $host_1 -message
+
+	    gdb_continue_to_breakpoint "host.1" ".*host.1.*"
+	    gdb_continue_to_breakpoint "function.single_task" \
+		".*kernel.single_task.*"
+
+	    # This part only works for kernels offloaded to a device.
+	    set symbol ""
+	    if {[require_sycl_device "$device" "gpu" "*"]} {
+		set symbol [find_kernel_symbol_gpu]
+	    }
+	    # Fall back to the original user kernel function symbol.
+	    if {$symbol eq ""} {
+		set symbol "SingleTask::operator ()"
+	    }
+
+	    # We do not know where the breakpoint ends up.
+	    gdb_breakpoint $symbol -message -allow-pending
+	    gdb_continue_to_breakpoint "kernel.single_task"
+
+	    # We're at the start of the second run of the first kernel.
+	    #
+	    # Set breakpoints for the rest of the tests for this kernel.
+	    # We stopped in the actual kernel, which may be located in a
+	    # different source file.
+	    gdb_breakpoint $srcfile:$kernel_1 -message
+	    gdb_breakpoint $srcfile:$kernel_2 -message
+	    gdb_breakpoint $srcfile:foo -message
+	    gdb_breakpoint $srcfile:bar -message
+	    gdb_breakpoint $srcfile:$foo_1 -message
+	    gdb_breakpoint $srcfile:$bar_1 -message
+
+	    gdb_continue_to_breakpoint "kernel.1" ".*kernel.1.*"
+	    gdb_continue_to_breakpoint "foo.entry" ".*foo.entry.*"
+	    gdb_continue_to_breakpoint "foo.1" ".*foo.1.*"
+	    gdb_continue_to_breakpoint "kernel.2" ".*kernel.2.*"
+	    gdb_continue_to_breakpoint "bar.entry" ".*bar.entry.*"
+	    gdb_continue_to_breakpoint "bar.1" ".*bar.1.*"
+	}
+
+	with_test_prefix "lambda" {
+	    # Test that we are not leaking breakpoints into or out of
+	    # kernels defined as lambdas.
+	    #
+	    # We need a breakpoint before
+	    #
+	    #   queue.single_task ([] ()
+	    #
+	    # because that line also contains the lambda definition.
+	    gdb_breakpoint $host_3 -message
+	    gdb_breakpoint $lambda_1 -message
+
+	    gdb_continue_to_breakpoint "host.3" ".*host.3.*"
+	    gdb_continue_to_breakpoint "lambda.1" ".*lambda.1.*"
+	}
+
+	with_test_prefix "parallel_for" {
+	    # Now move on to the next kernel, again with an intermediate
+	    # stop in the host process.
+	    gdb_breakpoint $srcfile:$host_2 -message
+
+	    gdb_continue_to_breakpoint "host.2" ".*host.2.*"
+	    gdb_continue_to_breakpoint "function.parallel_for" \
+		".*kernel.parallel_for.*"
+
+	    # This part only works for kernels offloaded to a device.
+	    set symbol ""
+	    if {[require_sycl_device "$device" "gpu" "*"]} {
+		set symbol [find_kernel_symbol_gpu]
+	    }
+	    # Fall back to the original user kernel function symbol.
+	    if {$symbol eq ""} {
+		set symbol "ParallelFor::operator ()"
+	    }
+
+	    # We only want to hit this once to not complicate the test.
+	    #
+	    # Until kernel.6, threads race to the next breakpoint, but we
+	    # only get one stop from the winner; the loser will silently
+	    # skip this breakpoint.
+	    #
+	    # This allows the test to be serial even though we're testing
+	    # a parallel_for kernel.  We may switch threads at every stop,
+	    # but we don't really care which thread stops.
+	    #
+	    # But first, we need to get back to the host process so the
+	    # other thread does not stop on this new breakpoint in this
+	    # kernel instance.
+	    gdb_breakpoint $srcfile:$host_4 -message -temporary
+	    gdb_continue_to_breakpoint "host.4" ".*host.4.*"
+	    #
+	    # Now we can add the kernel breakpoint for the second run.
+	    #
+	    # We do not know where the breakpoint ends up.
+	    gdb_breakpoint $symbol -message -temporary -allow-pending
+	    gdb_continue_to_breakpoint "kernel.parallel_for"
+
+	    # We're at the start of the second run.
+	    #
+	    # We're now running a kernel with two workgroups and hence two
+	    # threads and we're going to cover
+	    #
+	    #   - temporary breakpoints
+	    #   - disable breakpoints
+	    #   - delete breakpoints
+	    #
+	    # and we're making sure that only one thread stops.
+	    #
+	    # Set breakpoints for the rest of the tests.  We stopped in
+	    # the actual kernel, which may be located in a different
+	    # source file.
+	    gdb_breakpoint $srcfile:$kernel_3 -message -temporary
+	    gdb_breakpoint $srcfile:$kernel_4 -message
+	    set bp_dis [get_integer_valueof "\$bpnum" invalid "disable bp"]
+	    gdb_breakpoint $srcfile:$kernel_5 -message
+	    set bp_del [get_integer_valueof "\$bpnum" invalid "delete bp"]
+	    gdb_breakpoint $srcfile:$kernel_6 -message
+
+	    gdb_continue_to_breakpoint "kernel.3" ".*kernel.3.*"
+	    gdb_continue_to_breakpoint "kernel.4" ".*kernel.4.*"
+	    gdb_test_no_output "disable $bp_dis"
+
+	    gdb_continue_to_breakpoint "kernel.5" ".*kernel.5.*"
+	    gdb_test_no_output "delete $bp_del"
+
+	    # We hit kernel.6 once for each workgroup.
+	    gdb_continue_to_breakpoint "kernel.6.1" ".*kernel.6.*"
+	    gdb_continue_to_breakpoint "kernel.6.2" ".*kernel.6.*"
+
+	    gdb_test "continue" "$inferior_exited_re normally.*"
+	}
+    }
+}
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 6fb04869605..41f8c36d36b 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -7462,6 +7462,21 @@ proc gdb_compile_win32 {source dest type options} {
     }
 }
 
+# Build a SYCL program from SOURCE.  See prefatory comment for
+# gdb_compile, above, for discussion of the parameters to this proc.
+
+proc gdb_compile_sycl {source dest type options} {
+    set new_options {additional_flags=-fsycl}
+    lappend new_options {additional_flags=-fsycl-unnamed-lambda}
+    lappend new_options {c++}
+    lappend new_options {optimize=-O0}
+
+    # If the optimize option is given multiple times, only the last use is
+    # significant in DEJAGNU.  So, to make optimize option set in the test
+    # significant, input 'options' are appended to the end here.
+    return [gdb_compile $source $dest $type [concat $new_options $options]]
+}
+
 # Send a command to GDB.
 # For options for TYPE see gdb_stdin_log_write
 
@@ -9737,7 +9752,7 @@ proc build_executable_from_specs {testname executable options args} {
     set binfile [standard_output_file $executable]
 
     set func gdb_compile
-    set func_index [lsearch -regexp $options {^(pthreads|shlib|shlib_pthreads|openmp)$}]
+    set func_index [lsearch -regexp $options {^(pthreads|shlib|shlib_pthreads|openmp|sycl)$}]
     if {$func_index != -1} {
 	set func "${func}_[lindex $options $func_index]"
     }
diff --git a/gdb/testsuite/lib/intelgt-utils.exp b/gdb/testsuite/lib/intelgt-utils.exp
new file mode 100644
index 00000000000..952a3f55d10
--- /dev/null
+++ b/gdb/testsuite/lib/intelgt-utils.exp
@@ -0,0 +1,43 @@
+# Copyright 2020-2026 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Intelgt utility procedures.
+
+# The lock file used to ensure that only one GDB has access to the GPU
+# at a time.
+
+set intelgt_lock_filename intelgt-parallel.lock
+
+# Return non-zero if the instruction at ADDRESS is compact, 0 otherwise.
+
+proc is_compact_insn {address} {
+    # Check the CmptCtrl flag (bit 29).
+    set test "is compact insn"
+    set is_compact [get_integer_valueof \
+			"((unsigned char *)$address)\[3\] & 0x20" 0 $test]
+    return $is_compact
+}
+
+# Set the breakpoint bit of the instruction at ADDRESS.
+
+proc_with_prefix set_breakpoint_bit {address} {
+    # Set Bit 7 on a compacted instruction, Bit 30 on a full instruction.
+    set test "set bp bit"
+    if {[is_compact_insn $address]} {
+	gdb_test "print/x ((unsigned char *)$address)\[0\] |= 0x80" "" $test
+    } else {
+	gdb_test "print/x ((unsigned char *)$address)\[3\] |= 0x40" "" $test
+    }
+}
diff --git a/gdb/testsuite/lib/sycl-devices.cpp b/gdb/testsuite/lib/sycl-devices.cpp
new file mode 100644
index 00000000000..d6f0784d1a5
--- /dev/null
+++ b/gdb/testsuite/lib/sycl-devices.cpp
@@ -0,0 +1,106 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2025 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Utility file for SYCL test programs to get list of available devices.  */
+
+#include <sycl/sycl.hpp>
+#include <map>
+
+static std::string
+get_backend_name (sycl::backend backend_arg)
+{
+  std::string backend_name;
+
+  if (backend_arg == sycl::backend::opencl)
+    backend_name = "opencl";
+  else if (backend_arg ==  sycl::backend::ext_oneapi_level_zero)
+    backend_name = "ext_oneapi_level_zero";
+  else
+    {
+      std::cout << "SYCL: Unrecognized backend." << std::endl;
+      exit (1);
+    }
+
+  return backend_name;
+}
+
+static std::string
+get_device_type (sycl::info::device_type type)
+{
+  std::string type_name;
+
+  if (type == sycl::info::device_type::cpu)
+    type_name = "cpu";
+  else if (type == sycl::info::device_type::gpu)
+    type_name = "gpu";
+  else if (type == sycl::info::device_type::accelerator)
+    type_name = "accelerator";
+  else
+    {
+      std::cout << "SYCL: Unrecognized device type." << std::endl;
+      exit (1);
+    }
+
+  return type_name;
+}
+
+int
+main ()
+{
+  const std::vector<sycl::device> devices
+    = sycl::device::get_devices (sycl::info::device_type::all);
+
+  if (devices.empty ())
+    {
+      std::cout << "SYCL: Could not find any device" << std::endl;
+      exit (1);
+    }
+
+  std::map<std::string, int> device_types;
+
+  for (const sycl::device &device : devices)
+    {
+      const std::string backend_name
+	= get_backend_name (device.get_backend ());
+      if (backend_name == "")
+	continue;
+
+      const std::string dev_name
+	= device.get_info<sycl::info::device::name> ();
+      const std::string device_version
+	= device.get_info<sycl::info::device::version> ();
+      const std::string type
+	= get_device_type (device.get_info<sycl::info::device::device_type> ());
+
+      std::string dev_key {dev_name + ";" + backend_name + ";" + type + ";"
+	+ device_version};
+      device_types[dev_key]++;
+    }
+
+  std::cout << "SYCL: List of Target devices: [";
+  int index = 0;
+  for (const auto& [dev_key, count] : device_types)
+    {
+      index++;
+      std::cout << dev_key << ";" << count;
+      if (index < device_types.size ())
+	std::cout << ",";
+    }
+  std::cout << "]" << std::endl;
+
+  return 0;
+}
diff --git a/gdb/testsuite/lib/sycl-hello.cpp b/gdb/testsuite/lib/sycl-hello.cpp
new file mode 100644
index 00000000000..3d2b8de407d
--- /dev/null
+++ b/gdb/testsuite/lib/sycl-hello.cpp
@@ -0,0 +1,33 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2026 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <sycl/sycl.hpp>
+#include "sycl-util.cpp"
+
+int
+main (int argc, char *argv[])
+{
+  sycl::queue queue {get_sycl_queue (argc, argv)};
+  queue.single_task ([] ()
+    {
+      int a = 0; /* inside-kernel */
+    });
+
+  queue.wait ();
+
+  return 0;
+}
diff --git a/gdb/testsuite/lib/sycl-util.cpp b/gdb/testsuite/lib/sycl-util.cpp
new file mode 100644
index 00000000000..97c85c85c18
--- /dev/null
+++ b/gdb/testsuite/lib/sycl-util.cpp
@@ -0,0 +1,135 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2019-2026 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Utility file for SYCL test programs to enable explicit selection of
+   a SYCL device.  Include this file in each SYCL test program.  */
+
+#include <sycl/sycl.hpp>
+#include <iostream>
+#include <vector>
+
+static sycl::info::device_type
+get_device_type (const std::string &type_arg)
+{
+  sycl::info::device_type type;
+
+  if (type_arg.compare ("cpu") == 0)
+    type = sycl::info::device_type::cpu;
+  else if (type_arg.compare ("gpu") == 0)
+    type = sycl::info::device_type::gpu;
+  else if (type_arg.compare ("accelerator") == 0)
+    type = sycl::info::device_type::accelerator;
+  else
+    {
+      std::cout << "SYCL: Unrecognized device type '"
+		<< type_arg << "'" << std::endl;
+      exit (1);
+    }
+
+  return type;
+}
+
+static sycl::backend
+get_backend_type (const std::string &backend_arg)
+{
+  sycl::backend backend;
+
+  if (backend_arg.compare ("opencl") == 0)
+    backend = sycl::backend::opencl;
+  else if (backend_arg.compare ("ext_oneapi_level_zero") == 0
+	   || backend_arg.compare ("level_zero") == 0)
+    backend = sycl::backend::ext_oneapi_level_zero;
+  else
+    {
+      std::cout << "SYCL: Unrecognized backend '"
+		<< backend_arg << "'" << std::endl;
+      exit (1);
+    }
+
+  return backend;
+}
+
+static std::vector<sycl::device>
+get_sycl_devices (int argc, char *argv[])
+{
+  if (argc <= 3)
+    {
+      std::cout << "Usage: " << argv[0]
+		<< " <cpu|gpu|accelerator>"
+		<< " <device name substring>"
+		<< " <backend name opencl|level_zero>" << std::endl;
+      exit (1);
+    }
+
+  std::string type_arg {argv[1]};
+  std::string name_arg {argv[2]};
+  std::string backend_arg {argv[3]};
+
+  sycl::info::device_type type = get_device_type (type_arg);
+  sycl::backend backend_type = get_backend_type (backend_arg);
+
+  std::vector<sycl::device> devices = sycl::device::get_devices (type);
+
+  std::vector<sycl::device> filtered_devices;
+  for (const sycl::device &device : devices)
+    {
+      std::string dev_name = device.get_info<sycl::info::device::name> ();
+      std::string platform_name
+	= device.get_platform ().get_info<sycl::info::platform::name> ();
+      std::string version
+	= device.get_info<sycl::info::device::driver_version> ();
+      sycl::backend backend = device.get_backend ();
+
+      if (dev_name.find (name_arg) != std::string::npos
+	  && backend == backend_type)
+	filtered_devices.push_back (device);
+    }
+
+  if (filtered_devices.empty ())
+    {
+      std::cout << "SYCL: Could not select a device" << std::endl;
+      exit (1);
+    }
+
+  return filtered_devices;
+}
+
+static void
+print_device (const sycl::device &device)
+{
+  std::string dev_name
+    = device.get_info<sycl::info::device::name> ();
+  std::string platform_name
+    = device.get_platform ().get_info<sycl::info::platform::name> ();
+  std::string version
+    = device.get_info<sycl::info::device::driver_version> ();
+
+  std::cout << "[" << dev_name << "]"
+	    << " from [" << platform_name << "]"
+	    << " version [" << version << "]";
+}
+
+static sycl::queue
+get_sycl_queue (int argc, char *argv[])
+{
+  sycl::device device = get_sycl_devices (argc, argv)[0];
+  std::cout << "SYCL: Using device: ";
+  print_device (device);
+  std::cout << std::endl;
+
+  return sycl::queue {device}; /* return-sycl-queue */
+}
diff --git a/gdb/testsuite/lib/sycl.exp b/gdb/testsuite/lib/sycl.exp
new file mode 100644
index 00000000000..3f1878b54c0
--- /dev/null
+++ b/gdb/testsuite/lib/sycl.exp
@@ -0,0 +1,399 @@
+# Copyright 2019-2026 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Support library for testing SYCL GDB features
+#
+# A particular SYCL device can be selected by passing the SYCL program
+# three command-line arguments:
+#   1. the device type, whose value is in {cpu, gpu, accelerator}.
+#   2. a substring of the device name.
+#   3. backend name.
+#
+# To set these arguments properly, use a SYCL board file, and
+# make your test program select a queue via the get_sycl_queue
+# function in gdb.sycl/sycl-util.cpp.  See gdb.sycl/sycl-hello.cpp
+# for a sample SYCL program setup.
+
+load_lib intelgt-utils.exp
+
+if {![info exists OFFLOAD_DEVICE_GROUP]} {
+    set OFFLOAD_DEVICE_GROUP "cpu,gpu,accelerator"
+}
+
+verbose -log "OFFLOAD_DEVICE_GROUP is '$OFFLOAD_DEVICE_GROUP'"
+
+# Return true if the SYCL device selected via the board file
+# matches the arguments.  Otherwise return false.
+
+proc require_sycl_device {device type name} {
+    set args_list [sycl_get_device_args $device]
+    if {[llength $args_list] <= 2} {
+	return 0
+    }
+
+    set type_match [expr {$type eq [lindex $args_list 0]}]
+    set name_match [string match $name [lindex $args_list 1]]
+
+    return [expr {$type_match && $name_match}]
+}
+
+# Run a test on the target to check if it recognizes SYCL.
+# Remove device from the available devices list if SYCL is not supported
+# and return the updated list.
+
+proc get_sycl_supported_devices {sycl_device_list} {
+    global srcdir inferior_exited_re
+
+    set supported_sycl_device_list {}
+
+    # Set up, compile, and execute a simple SYCL program.
+    set exe [standard_output_file sycl-hello]
+    set src "$srcdir/lib/sycl-hello.cpp"
+
+    if {[build_executable "failed to compile $src" $exe $src {sycl debug}]} {
+	verbose "SYCL: Compilation failed" 0
+	return $supported_sycl_device_list
+    }
+    verbose -log "\nSYCL: Compilation succeeded"
+
+    foreach device $sycl_device_list {
+	if {![is_sycl_device_filtered $device]} {
+	    continue
+	}
+
+	sycl_with_intelgt_lock $device {
+	    clean_restart sycl-hello
+
+	    if {![sycl_start $device]} {
+		verbose "SYCL: Support not detected for $device" 0
+		continue
+	    }
+
+	    set inside_kernel [gdb_get_line_number "inside-kernel" $src]
+	    gdb_breakpoint "sycl-hello.cpp:$inside_kernel"
+
+	    set result 1
+	    gdb_test_multiple "continue" "continue" {
+		-re -wrap "$inferior_exited_re normally].*" {
+		    set result 1
+		}
+		-re -wrap "$inferior_exited_re with code.*" {
+		    set result 1
+		}
+		-re -wrap "(?:Breakpoint) .* (at|in).*sycl-hello.cpp:$inside_kernel.*" {
+		    set result 0
+		}
+		-re -wrap "received signal SIGABRT, Aborted.*" {
+		    set result 1
+		}
+	    }
+
+	    if {$result == 0} {
+		verbose "SYCL: Support detected for $device" 0
+		lappend supported_sycl_device_list "$device"
+	    } else {
+		verbose "SYCL: Support not detected for $device" 0
+	    }
+	}
+    }
+
+    return $supported_sycl_device_list
+}
+
+# Run the program under debug by passing DEVICE as the command line
+# argument.  If the device is not Intel GT, stop at main.  If the
+# device is Intel GT, continue until the zeContextCreate API call and
+# attempt to create another inferior connected to an Intel GT
+# gdbserver target.
+#
+# Return 1 on success, 0 on failure.
+
+proc sycl_start {device} {
+    if {[require_sycl_device $device "gpu" "Intel*"]} {
+	# To debug an Intel GT device, we create an additional
+	# inferior.  For the multi-target setting to work, we
+	# need to operate in target-non-stop mode.
+	gdb_test_no_output "maint set target-non-stop on"
+    }
+
+    set args ""
+    foreach arg [sycl_get_device_args $device] {
+	append args "'$arg' "
+    }
+    gdb_test_no_output "set args $args"
+
+    if {![runto_main]} {
+	return 0
+    }
+
+    if {[require_sycl_device $device "gpu" "Intel*"]} {
+	# For the Intel GT target, define a "hook" BP at a
+	# Level-Zero API function at which the Level-Zero backend
+	# would have been already initialized, attaching to the
+	# device would be expected to succeed.  Once we hit the
+	# hook BP, we create the additional inferior.
+	gdb_breakpoint "zeModuleCreate" {*}"-allow-pending -temporary"
+	set hook_bp_hit [gdb_continue_to_breakpoint "hook bp" \
+			     "zeModuleCreate\[^\r\n\]*"]
+	if {$hook_bp_hit != 0} {
+	    return 0
+	}
+
+	with_test_prefix "$device" {
+	    set inf_pid [get_inferior_pid]
+	    gdb_test "add-inferior -no-connection"
+	    gdb_test "inferior 2"
+	    set cmd "gdbserver-intelgt --once --multi -"
+	    gdb_test "target extended-remote | $cmd" \
+		"Remote debugging.*" "connect the remote target"
+	    gdb_test "attach $inf_pid"
+	    gdb_test "inferior 1"
+	    gdb_test "set schedule-multi on"
+	    gdb_test "info inferior" ".*" "inferiors for logging"
+	}
+    }
+
+    return 1
+}
+
+# Get list of devices and return 0 if device list is non-empty else
+# return 1.  Each device entry of this list contains ";" separated
+# following information:
+# device name;backend name;device type;device version;device count.
+
+gdb_caching_proc init_sycl_devices_list {} {
+    global srcdir
+    global inferior_exited_re
+    global sycl_device_list
+
+    set sycl_device_list {}
+    set supported_sycl_device_list {}
+
+    # Set up, compile, and execute a simple SYCL program.
+    set exe [standard_output_file sycl-devices]
+    set src "$srcdir/lib/sycl-devices.cpp"
+
+    if {![test_compiler_info {icx-*} c++]} {
+	unsupported "SYCL tests supported only for dpcpp and icpx compilers"
+	return $sycl_device_list
+    }
+
+    if {[build_executable "failed to compile $src" $exe $src {sycl debug}]} {
+	verbose "SYCL: Compilation failed" 0
+	return $sycl_device_list
+    }
+    verbose -log "\nSYCL: Compilation succeeded"
+
+    clean_restart sycl-devices
+
+    if {![runto_main]} {
+	untested "failed to run sycl-devices to main"
+	return $sycl_device_list
+    }
+
+    set result 1
+    gdb_test_multiple "continue" "continue" {
+	-re "SYCL: List of Target devices: \\\[(\[^\r\n\]+)\\\]" {
+	    set sycl_device_list [split $expect_out(1,string) ","]
+	    exp_continue
+	}
+	-re -wrap "$inferior_exited_re normally].*" {
+	    set result 0
+	}
+	-re -wrap "$inferior_exited_re with code.*" {
+	    set result 1
+	}
+    }
+
+    set supported_sycl_device_list [get_sycl_supported_devices $sycl_device_list]
+    if {($result == 0) && ([llength $supported_sycl_device_list] > 0)} {
+	verbose "SYCL: Devices found: $supported_sycl_device_list" 0
+    } else {
+	set result 1
+	verbose "SYCL: No device found" 0
+    }
+
+    gdb_exit
+
+    return $supported_sycl_device_list
+}
+
+# Return the ID of the current thread (<inferior number>.<thread
+# number>).  This procedure can be more practical than using the
+# $_thread and $_inferior convenience variables, because if the SYCL
+# kernel is offloaded to a CPU target, the current thread would be a
+# single integer, but if offloaded to a GPU, it may be an
+# inferior-qualified number like N.M.
+proc get_current_thread {location} {
+    global decimal
+
+    gdb_test_multiple "thread" "get current thread at $location" {
+	-re -wrap "Current thread is ($decimal|$decimal\.$decimal).*" {
+	    pass $gdb_test_name
+	    return $expect_out(1,string)
+	}
+	-re -wrap "" {
+	    fail $gdb_test_name
+	}
+    }
+    return 0
+}
+
+# Returns 1 if the target device is selected via OFFLOAD_DEVICE_GROUP
+# and 0 otherwise.
+# DEVICE contains ";" separated following information:
+# device name;backend name;device type;device version;device count.
+
+proc is_sycl_device_filtered {device} {
+    global OFFLOAD_DEVICE_GROUP
+
+    # Filter according to OFFLOAD_DEVICE_GROUP.
+    set device_info [split "$device" ";"]
+    set backend [lindex $device_info 1]
+    set device_type [lindex $device_info 2]
+
+    if {[lsearch -nocase [split $OFFLOAD_DEVICE_GROUP ","] $device_type] < 0} {
+	verbose -log "SYCL: device type $device_type is unwanted, skipping '$device'"
+	return 0
+    }
+
+    if {$device_type == "gpu"
+	&& [string match -nocase "*opencl*" $backend]} {
+	verbose -log "SYCL: unsupported combination: $device_type & $backend"
+	return 0
+    }
+
+    return 1
+}
+
+# Returns number of devices found in device string.
+# DEVICE contains ";" separated following information:
+# device name;backend name;device type;device version;device count.
+
+proc sycl_get_device_count {device} {
+    set device_info [split "$device" ";"]
+    set device_count [lindex $device_info 4]
+    return $device_count
+}
+
+# Gets the list of args required for running the SYCL tests, where input device
+# contains ";" separated following information:
+# device name;backend name;device type;device version;device count.
+#
+# Returns [device type, device name, backend name, device version]
+proc sycl_get_device_args {device} {
+    global hex
+
+    set device_info [split "$device" ";"]
+    set backend_name [lindex $device_info 1]
+    set device_type [lindex $device_info 2]
+    set device_version [lindex $device_info 3]
+    set device_name ""
+    set args_list {}
+
+    if {$device_type eq "gpu"} {
+	lappend args_list "gpu"
+	lappend args_list [lindex $device_info 0]
+    } elseif {$device_type eq "cpu"} {
+	lappend args_list "cpu"
+	if {[string match "*Intel*" $device]} {
+	    lappend args_list "Intel"
+	}
+    } elseif {$device_type eq "accelerator"} {
+	lappend args_list "accelerator"
+	if {[string match "*Intel*" $device]} {
+	    lappend args_list "Intel"
+	}
+    } else {
+	verbose "SYCL: Unexpected device type: $device_type" 0
+    }
+    lappend args_list $backend_name
+    lappend args_list $device_version
+    return $args_list
+}
+
+# Gets the prefix string required for the SYCL tests.
+#
+# Function returns ":" separated test prefix which has following info:
+# In case of non GPU device: Device type:Backend type:cpp
+# and in case of GPU: Device type GPU: Backend type: Graphics device ID
+# e.g. gpu:opencl:{0x1234}
+
+proc sycl_get_device_prefix {device} {
+    global hex
+    set args_list [sycl_get_device_args $device]
+
+    if {[string match -nocase "*Graphics*" $device]
+	 || [string match -nocase "*GPU*" $device]} {
+	    # In case of GPU device, add device ID to the prefix to get a unique
+	    # test name for multi GPU test machines.
+	    return "[lindex $args_list 0]:[lindex $args_list 2]:\
+		    {[regexp -all -inline $hex [lindex $args_list 1]]}"
+    }
+    return "[lindex $args_list 0]:[lindex $args_list 2]:cpp"
+}
+
+# Run BODY under the lock, if DEVICE is an Intel GPU.  Also calls
+# gdb_exit before releasing the GPU lock.
+#
+# See the similar 'with_gpu_lock' in rocm.exp.
+
+proc sycl_with_intelgt_lock {device body} {
+    with_test_prefix [sycl_get_device_prefix $device] {
+	if {![require_sycl_device "$device" "gpu" "Intel*"]} {
+	    set code [catch {uplevel 1 $body} result]
+	} else {
+	    with_lock $::intelgt_lock_filename {
+		set code [catch {uplevel 1 $body} result]
+	    }
+	}
+
+	# In case BODY returned early due to some testcase failing.
+	gdb_exit
+    }
+
+    if {$code == 1} {
+	return -code $code -errorinfo $::errorInfo \
+	    -errorcode $::errorCode $result
+    } else {
+	return -code $code $result
+    }
+}
+
+# Get the namespace version for the SYCL header corresponding to the compiler
+# used.  Return 0 for older compilers using SYCL without namespace versioning.
+
+proc get_sycl_header_version {} {
+    if {[test_compiler_info {icx-202[3-9]-*} c++]} {
+	return 1
+    }
+
+    return 0
+}
+
+# Spawn a SYCL program targeting DEVICE.
+
+proc spawn_sycl_proc {executable device} {
+    # We directly use 'remote_spawn' to be able to pass
+    # the program arguments.
+    set command [list $executable]
+    foreach arg [sycl_get_device_args $device] {
+	lappend command $arg
+    }
+    verbose -log "command: $command"
+
+    set spawn_id [remote_spawn target $command]
+    return $spawn_id
+}
-- 
2.43.0

________________________________________
Intel Deutschland GmbH 

Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany 

Tel: +49 (89) 99143-0 

www.intel.de 

Managing Directors: Candice Moore, Jeffrey Schneiderman, Ramachandran Sitaraman

Chairperson of the Supervisory Board: Sonja Pierer

Registered Seat: Munich Commercial Register B: Amtsgericht Munich HRB 186928

This e-mail and any attachments may contain confidential material for
the sole use of the intended recipient(s). Any review or distribution
by others is strictly prohibited. If you are not the intended
recipient, please contact the sender and delete all copies.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.