# Copyright 2021-2024 Free Software Foundation, Inc.
# Copyright (C) 2021-2024 Advanced Micro Devices, Inc. All rights reserved.
# 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 .
# This file is a dejagnu "board file" and is used to run the testsuite
# against C/C++ device code compiled with amdclang++ (HIP).
#
# Example usage:
# bash$ make check RUNTESTFLAGS='--target_board=hip'
# Running tests against the GPU device is accomplished with linking
# lib/hip/hip-driver.cc into every testcase. That file contains both
# the host's main() entry point, and a kernel entry point. The kernel
# calls the testcase's main, in GPU context.
#
# Then the board overrides gdb_compile to compile the C/C++ testcase
# files targeting the device, and link in the driver.
#
# Ideally we would not have to modify any of the testcases sources.
# Unfortunately that isn't possible, but we can avoid a number of
# classes of changes, effectively reducing required absolute changes
# significantly.
#
# For example, to force the compiler to emit __device__ functions
# without sprinkling __device__ all over the place in all functions in
# all testcases, we use:
#
# #pragma clang force_cuda_host_device begin
#
# And to force the compiler to emit __device__ globals, we use:
#
# #pragma clang attribute (__device__, apply_to = variable (is_global))
#
# The board file tweaks the compilation routine to inject that in
# translation units by compiling a wrapper hip-test-wrapper.cc instead
# of the desired file. The wrapper file does the magic mentioned
# above and includes the file we wanted to compile.
#
# The hip-test-wrapper.cc file is also responsible for renaming the
# testcase's main function, so that it doesn't conflict with the
# host's main:
#
# #define main gdb_hip_test_main
#
# The testsuite, for each testcase, normally compiles the test
# program, and then runs it to main (like "b main; run"). Because we
# rename the device/testcase "main" function, "b main" sets a
# breakpoint on the host "main" function, not the testcase's entry
# point. That is addressed by having GDB itself rename the
# "gdb_hip_test_main" function at symbol read time, pretending that
# it's really called "main". The host "main" function is also renamed
# to get it out of the way.
#
# Parallel mode testing is supported by making use of the file locking
# implemented in lib/rocm.exp and also used by the gdb.rocm/
# testcases. That means that only one GDB at a time runs, but, most
# compilations run in parallel, which still provides a speed
# improvement, since compiling for the GPU is slow.
#
# Note that HIP is C++ based, so we're running all C testcases in C++
# mode. E.g., most gdb.base/ testcases.
#
# The multi-threading testcases are disabled. The device kernel runs a
# single work-item.
load_lib rocm.exp
load_generic_config "unix"
process_multilib_options ""
set_board_info compiler [find_hip_compiler]
set_board_info c++compiler [find_hip_compiler]
set_board_info gdb,cannot_call_functions 1
set_board_info gdb,can_reverse 0
set_board_info gdb,use_precord 0
# Override the target set in build/gdb/testsuite/site.exp.
set target_alias amdgcn-amd-amdhsa
set target_triplet amdgcn-amd-amdhsa
# This is needed otherwise dejagnu tries to rsh to host "hip".
load_board_description "local-board"
# So we can override skip_gdbserver_tests.
load_lib gdbserver-support.exp
# Tell GDB to enable the HIP test mode. This renames the main and
# gdb_hip_test_name functions to provide the illusion that the
# kernel's main is the real main.
set ::env(GDB_HIP_TEST_MODE) 1
proc skip_gdbserver_tests {} {
return 1
}
proc skip_fortran_tests {} {
return 1
}
proc skip_ada_tests {} {
return 1
}
proc skip_go_tests {} {
return 1
}
proc skip_d_tests {} {
return 1
}
proc skip_rust_tests {} {
return 1
}
proc skip_shlib_tests {} {
return 1
}
proc skip_btrace_tests {} {
return 1
}
proc skip_compile_feature_tests {} {
return 1
}
# So we can override skip_opencl_tests.
load_lib "opencl.exp"
proc skip_opencl_tests {} {
return 1
}
# Yup, we don't want to run the gdb.rocm/ tests against this board.
# That would be kind of recursive. Those tests really expect that
# "main()" is host code and start kernels themselves.
proc allow_hip_tests {} {
return 0
}
# Override this to fail faster. If we let the default run, tests
# compile successfully, but fail at link time.
proc gdb_compile_pthreads {source dest type options} {
unsupported "skipped compiling [file tail $source]: pthreads tests disabled on HIP"
return -1
}
proc do_gpu_lock_release {} {
global gpu_lock_rc
if {[info exists gpu_lock_rc]} {
lock_file_release $gpu_lock_rc
unset gpu_lock_rc
}
}
# Because building the testcases is so slow, it's quite a speed boost
# to run the testsuite in parallel mode, so that multiple programs are
# compiled at the same time. However, only one GDB may be using the
# GPU at a time. So we use a lock file to synchronize access to the
# GPU -- we acquire the GPU lock when GDB is started, and release it
# when GDB exits. Generally testcases compile their programs before
# starting GDB. We hook into gdb_start and gdb_exit here to do just
# that.
if {[info exists GDB_PARALLEL]} {
if { [info procs hip_org_gdb_spawn] == "" } {
rename gdb_spawn hip_org_gdb_spawn
}
proc gdb_spawn {} {
global gdb_spawn_id
if ![info exists gdb_spawn_id] {
global gpu_lock_filename
global gpu_lock_rc
set gpu_lock_rc [lock_file_acquire $gpu_lock_filename]
}
set res [hip_org_gdb_spawn]
if {$res != 0} {
lock_file_release $gpu_lock_rc
unset gpu_lock_rc
}
return $res
}
if { [info procs hip_org_gdb_exit] == "" } {
rename gdb_exit hip_org_gdb_exit
}
proc gdb_exit {} {
verbose -log "hip: gdb_exit"
set res [hip_org_gdb_exit]
do_gpu_lock_release
return $res
}
if { [info procs hip_org_mi_gdb_exit] == "" } {
rename mi_gdb_exit hip_org_mi_gdb_exit
}
proc mi_gdb_exit {} {
verbose -log "hip: mi_gdb_exit"
set res [hip_org_mi_gdb_exit]
do_gpu_lock_release
return $res
}
}
# As a speed optimization, avoid recompiling the driver over and over
# for each testcase program.
global gdb_saved_hip_driver_obj
set gdb_saved_hip_driver_obj ""
# True if compiling lib/hip/hip-driver.cc. If true, we don't want to
# use the hip-test-wrapper.cc wrapper.
global compiling_hip_driver
set compiling_hip_driver 0
# True if calling rocm_assemble. If true, we don't want to force
# compilation targeting the device.
global calling_rocm_assemble
set calling_rocm_assemble 0
proc hip_get_compile_language {options} {
foreach lang {c++ rust ada f77 f90 go hip} {
if {[lsearch -exact $options $lang] != -1} {
return $lang
}
}
return c
}
# Override gdb_compile, to inject our driver, which has a host main()
# function that starts a device kernel that calls the testcase's entry
# point (main renamed to gdb_hip_test_main).
if { [info procs hip_org_gdb_compile] == "" } {
rename gdb_compile hip_org_gdb_compile
}
proc gdb_compile {source dest type options} {
global srcdir objdir
global gdb_saved_hip_driver_obj
global compiling_hip_driver
global calling_rocm_assemble
set lang [hip_get_compile_language {$options}]
if {[lsearch -exact $options getting_compiler_info] == -1
&& !$calling_rocm_assemble
&& ($lang == "c" || $lang == "c++")} {
# We need:
#
# . -std=gnu++11 because some testcases rely on GNU extensions.
#
# . -fgpu-rdc enables separate compilation mode, so we can
# compile the kernel from multiple translation units.
#
# . -amdgpu-spill-cfi-saved-regs makes backtraces actually
# work.
#
# . -Wno-unused-command-line-argument is needed otherwise we
# get this warning during linking, even though the option is
# really needed when linking:
#
# clang: warning: argument unused during compilation:
# '-mllvm -amdgpu-spill-cfi-saved-regs'
# [-Wunused-command-line-argument]
#
# When ROCM_PATH is set in the environment, pass --rocm-path
# so amdclang++ uses that specific ROCm install for HIP.
# When unset, let amdclang++ auto-discover (parent of its own
# LLVM dir, falling back on /opt/rocm).
set rocm_path_arg ""
if {[info exists ::env(ROCM_PATH)] && $::env(ROCM_PATH) ne ""} {
set rocm_path_arg "--rocm-path=$::env(ROCM_PATH)"
}
set hip_options "early_flags=$rocm_path_arg\
-std=gnu++11\
-fgpu-rdc\
-mllvm -amdgpu-spill-cfi-saved-regs\
-Wno-unused-command-line-argument"
global CC_FOR_TARGET
if {[board_info target exists compiler]} {
set compiler [target_info compiler]
} elseif {[info exists CC_FOR_TARGET]} {
set compiler $CC_FOR_TARGET
}
if {[string first "amdclang++" $compiler] != -1} {
# Remove "c++" from the options when linking, otherwise we
# would get:
#
# build/gdb/testsuite/hip-driver.o:1:1: error: expected unqualified-id
# ELF
#
# ... because -x c++ would make Clang blindly treat the
# input .o files as C++.
#
set idx [lsearch -exact $options "c++"]
set options [lreplace $options $idx $idx]
# Since we wrap translation units with hip-test-wrapper.cc
# when compiling C/C++ source files (see below), we can't
# mix source and object input files in the same
# compilation. Fix that by forcing separate compilation
# for each input file that doesn't look like an object
# file, and replacing the corresponding SOURCE array
# element with the corresponding built object file.
if { $type == "executable" } {
set new_source {}
foreach src $source {
if {[file extension $src] != ".o"} {
set out [standard_temp_file [file tail $src].o]
set res [gdb_compile $src $out object $options]
if {$res != ""} {
return $res
}
lappend new_source $out
} else {
lappend new_source $src
}
}
set source $new_source
# Compile the driver.
if { $gdb_saved_hip_driver_obj == "" } {
verbose "compiling hip-driver"
set hip_driver_src ${srcdir}/lib/hip/hip-driver.cc
set hip_driver_obj ${objdir}/hip-driver.o
set compiling_hip_driver 1
set result [gdb_compile "${hip_driver_src}" "${hip_driver_obj}" object {debug}]
set compiling_hip_driver 0
if { $result != "" } {
return $result
}
if {[is_remote host]} {
set gdb_saved_hip_driver_obj hip-driver.o
} else {
set gdb_saved_hip_driver_obj ${objdir}/hip-driver.o
}
# Link a copy of the output object, because the
# original may be automatically deleted.
remote_download host $hip_driver_obj $gdb_saved_hip_driver_obj
}
# Prefix "-x none" so amdclang++ does not treat the
# driver .o as HIP source (the preceding inputs on
# the command line are .cpp tagged "-x hip").
lappend options "ldflags=-x none $gdb_saved_hip_driver_obj"
lappend options $hip_options
lappend options "early_flags=--hip-link"
# All compiled. Now link, and we're done.
return [hip_org_gdb_compile $source $dest $type $options]
}
# Compiling some source file to an object file.
# If we're not linking, then we're guaranteed to only have
# one source file, due to the recursion in the
# "executable" case above.
if {[llength $source] != 1} {
error "more than one source file?"
}
# See if we're actually compiling an assembly file.
set src [lindex $source 0]
if {[file extension $src] == ".S" || [file extension $src] == ".s"} {
set calling_rocm_assemble 1
set res [rocm_assemble $src $dest $options]
set calling_rocm_assemble 0
return $res
}
# Make HIP/C++ look a little bit more like C.
if {$lang == "c"} {
append hip_options "\
-Wno-writable-strings\
-Wno-c++11-narrowing"
}
# Clang automatically includes
# __clang_hip_runtime_wrapper.h via -include on the
# command line. We need to override the malloc definition
# so we need to wrap the wrapper header... Our wrapper
# lives in the lib/hip/ dir.
append hip_options " -I ${srcdir}/lib/hip/"
if {!$compiling_hip_driver} {
# Wrap the file we actually want to compile with
# hip-test-wrapper.cc.
append hip_options " -DINC_TEST_FILE=\"$source\""
set source "${srcdir}/lib/hip/hip-test-wrapper.cc"
}
lappend options $hip_options
lappend options "early_flags=-x hip"
}
}
hip_org_gdb_compile $source $dest $type $options
}