Files
qemu/tests/tcg/meson.build
T
Zephyr Li 57b484e2d2 target/riscv: fix RV32 fixed counter accesses
Since commit cfc96df65e, riscv_pmu_ctr_get_fixed_counters_val()
returns the complete 64-bit fixed-counter value. The RV32 counter
access paths, however, still perform parts of the offset calculation
on separately extracted 32-bit halves.

In particular, riscv_pmu_write_ctrh() deposits the low 32 bits of the
complete fixed-counter value into the high half of mhpmcounter_prev.
riscv_pmu_read_ctr() also subtracts a 32-bit half of the previous value
from the complete 64-bit fixed-counter value. Consequently, writes to
mcycleh can be lost and carries between the low and high halves are not
handled correctly.

Keep the fixed-counter offset calculation entirely in 64 bits. Before
a running counter is partially written, materialize its current
architectural value and reset the fixed-counter baseline. On reads,
calculate the complete 64-bit counter value before extracting the half
requested by RV32.

Register RV32 system TCG tests with the Meson build. Add a test for
high-half writes, low-to-high carry, and preserving the carried high
half across a subsequent low-half write.

Fixes: cfc96df65e ("target/riscv: Remove upper_half from riscv_pmu_ctr_get_fixed_counters_val")
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4219
Signed-off-by: Zephyr Li <fritchleybohrer@gmail.com>
Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
Reviewed-by: Chao Liu <chao.liu@processmission.com>
Message-ID: <20261008035150.2223-1-fritchleybohrer@gmail.com>
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
2026-10-16 10:40:39 +10:00

550 lines
18 KiB
Meson

env = find_program('env')
docker_wrapper = find_program('../docker/docker.py')
if get_option('gdb') != ''
gdb = find_program(get_option('gdb'), required: true)
else
gdb = find_program(['gdb-multiarch', 'gdb'], required: false)
endif
prog_check_plugin_output = find_program('./scripts/check_plugin_output.sh')
prog_gdb_test = find_program('../guest-debug/run-test.py')
prog_record_replay = find_program('./scripts/record_replay.sh')
prog_run_and_diff = find_program('./scripts/run_and_diff.sh')
prog_run_with_input = find_program('./scripts/run_with_input.sh')
prog_run_and_check_forbidden_output = find_program(
'./scripts/run_and_check_forbidden_output.sh'
)
tcg_tests = {}
# tcg_tests is a dictionary with following structure:
#
# {
# 'name_of_target': {
# 'cc': 'cross_compiler',
# 'cc_alt': ['list of alternative cross compilers', ...]
# 'cc_dockerfile': 'name_of_dockerfile',
# 'cc_feat_cflags': {
# 'name': ['feature cflags'],
# ...
# },
# 'folder': 'folder_for_test_src_files',
# 'gdb_arch': 'gdb architecture requirement',
# 'gdb_feat_version': {
# 'name': ['minimal gdb version supporting feature'],
# ...
# },
# 'qemu': emulators['qemu-...'],
# 'tests': [
# {
# 'src_file': {
# 'exe_name': ['provide an alternative binary name'],
# 'test_name': ['provide an alternative test name'],
# 'cflags': ['additional cflags to compile test', ...],
# 'depends': [meson_dependency, ...],
# 'cc_feat': 'feature name, must be present in cc_feat_cflags',
# 'qemu_args': ['qemu command line flags', ...],
# 'expected_output': 'file_to_compare_output',
# 'gdb_test': ['gdb test args'],
# 'gdb_feat': ['feature name, must be present in gdb_feat_version'],
# 'env_var': ['VAR=value', ...],
# 'wrapper': [program_to_launch_test, 'args'...],
# 'plugin_test': {
# 'plugin': 'plugin_name',
# 'args': ['plugin_args', ...],
# },
# }
# },
# ...
# ]
# }
# }
#
# Every test executable, identified by 'exe_name' is built only once, and we
# ensure cflags are specified only once, on first declaration.
# Tests for a given src use the same executable by default, and their definition
# is guaranteed to be unique also. Several tests for the same executable can
# declared using 'test_name'.
# Default name is derived from src if 'exe_name' and 'test_name' are omitted.
gdb_arch_supported = {}
gdb_version = '0'
if gdb.found()
gdb_version = gdb.version()
gdb_arch = run_command(python, files('../../scripts/probe-gdb-support.py'),
gdb,
check: true)
foreach arch: gdb_arch.stdout().split()
gdb_arch_supported += {arch: true}
endforeach
endif
tcg_tests_summary += {'gdb': gdb}
docker_supported = false
container_command = false
docker_platform = ['--platform', 'linux/amd64']
if get_option('containers')
if get_option('container_command') != ''
# make sure command is working
container_command = get_option('container_command')
cmd = container_command.split()
run_command([cmd, 'info'], check: true)
docker_supported = true
docker_wrapper = [docker_wrapper, '--command',
container_command]
else
docker_probe = run_command(docker_wrapper, 'probe', check: false)
docker_supported = docker_probe.returncode() == 0
if docker_supported
container_command = docker_probe.stdout().strip()
docker_wrapper = [docker_wrapper, '--command',
container_command]
endif
endif
if docker_supported
# Check that x86_64 containers can be executed. It implies xthat 86_64
# binaries can be executed. On host with a different architecture,
# this is usually achieved with qemu-user-binfmt.
docker_supported = run_command([docker_wrapper, 'run', '--',
docker_platform,
'docker.io/amd64/debian:13-slim',
'/bin/true'],
check: false).returncode() == 0
tcg_tests_summary += {'x86_64 container support': docker_supported}
endif
endif
tcg_tests_summary += {'container command': container_command}
if get_option('container_registry') != ''
tcg_tests_summary += {'container registry': get_option('container_registry')}
endif
# plugins come first, as we need to build the list
test_plugins = {}
subdir('plugins')
# multiarch comes second, so it can be referenced by other arch after
subdir('multiarch')
# Generic Plugin tests
# add a single test for each plugin in user/system multiarch set
plugin_tests_exclude = ['patch']
foreach target: ['multiarch-linux-user', 'multiarch-softmmu']
plugin_tests = []
tests = tcg_tests[target]['tests']
normal_tests = []
# filter tests that are not plugin or gdb tests
foreach t: tests
foreach src, setup: t
if 'plugin_test' in setup or 'gdb_test' in setup
continue
endif
normal_tests += {src: setup}
endforeach
endforeach
idx = 0
foreach plugin_name, _: test_plugins
if plugin_name in plugin_tests_exclude
continue
endif
# pick one different test for each plugin
foreach src, setup: normal_tests[idx % normal_tests.length()]
# copy existing setup and remove existing cflags
new_setup = {}
foreach key, val: setup
if key == 'cflags'
continue
endif
new_setup += {key: val}
endforeach
new_setup += {'plugin_test': {'plugin': plugin_name}}
plugin_tests += {src: new_setup}
endforeach
idx += 1
endforeach
# replace list of tests with old and new ones
tcg_tests += {target: {'tests': tests + plugin_tests}}
endforeach
# Now let's go through all architectures
subdir('aarch64')
subdir('aarch64_be')
subdir('alpha')
subdir('arm')
subdir('hexagon')
subdir('hppa')
subdir('i386')
subdir('loongarch64')
subdir('m68k')
subdir('mips')
subdir('mipsel')
subdir('mips64')
subdir('mips64el')
subdir('or1k')
subdir('ppc64')
subdir('ppc64le')
subdir('riscv32')
subdir('riscv64')
subdir('s390x')
subdir('sh4')
subdir('tricore')
subdir('x86_64')
subdir('xtensa')
image_targets = {}
exe_targets = []
# Finally, we can create all test executables and test targets
foreach target, plan: tcg_tests
if target.startswith('multiarch-')
# those tests are included by architecture files if needed
continue
endif
cc_arch = target.replace('-linux-user', '').replace('-softmmu', '')
# Detect duplicated executables/tests, and report an error to force user to
# choose how to deal with it.
built_tests = {}
added_tests = {}
cc_has_feat = {}
# return a clear error if user misspell a target entry
foreach key, _ : plan
allowed = ['cc', 'cc_alt', 'cc_dockerfile', 'cc_feat_cflags',
'folder', 'gdb_arch', 'gdb_feat_version', 'qemu', 'tests']
if key not in allowed
error('unknown tcg test plan entry \'' + key + '\' for target ' + target +
' (possible: [' + ', '.join(allowed) + '])')
endif
endforeach
cc_list = [plan['cc']]
if 'cc_alt' in plan
cc_list += plan['cc_alt']
endif
cc_cflags = []
check_flags = []
check_required = false
cross_cc = get_option('tcg_tests_cross_cc_' + cc_arch)
if cross_cc != ''
# prepend requested cross_cc
cc_list = [cross_cc, cc_list]
check_required = true
endif
cross_cflags = get_option('tcg_tests_cross_cflags_' + cc_arch)
if cross_cflags != ''
cc_cflags = cross_cflags.split()
# and that cross cc can compile programs
check_flags = cc_cflags
check_required = true
endif
if target.endswith('softmmu')
check_flags += ['-nostdlib', '-ffreestanding', '-r']
else
check_flags += ['-static']
endif
has_cc = false
foreach possible_cc: cc_list
cc = find_program(possible_cc, required: check_required)
if cc.found()
cmd = run_command([cc, files('test_cc.c'), check_flags, '-o', '/dev/null'],
check: check_required)
if cmd.returncode() == 0
has_cc = true
break
endif
endif
endforeach
cc_from_system = has_cc
build_test_depends = []
build_test_depend_files = []
if 'cc_dockerfile' in plan
cc_dockerfile = plan['cc_dockerfile']
dockerfile = files('..'/'docker'/'dockerfiles'/cc_dockerfile + '.docker')
image_name = 'image-' + cc_dockerfile
if cc_dockerfile not in image_targets and docker_supported
cmd = [docker_wrapper, 'build', '-f', dockerfile, '-t', 'qemu/' + cc_dockerfile,
docker_platform, '--add-current-user']
if get_option('container_registry') != ''
cmd += ['--registry', get_option('container_registry')]
endif
t = custom_target(image_name, command: cmd,
build_by_default: false,
console: true,
output: 'no_output_' + image_name)
image_targets += {cc_dockerfile: t}
endif
if not has_cc and docker_supported
build_test_depends += image_targets[cc_dockerfile]
build_test_depend_files += dockerfile
mount = meson.project_source_root()
mount = mount + ':' + mount
here = meson.project_build_root()
cc = [docker_wrapper, 'run', '--run-as-current-user', '--',
# SELinux blocks modification of host fs by default
'--security-opt', 'label=disable',
docker_platform,
'-w', here, '-v', here + ':' + here, '-v', mount,
'qemu/' + cc_dockerfile, plan['cc']]
has_cc = true
endif
endif
summary_cc = false
if has_cc
if cc_from_system
summary_cc = cc.full_path()
else
summary_cc = plan['cc'] + ' (from \'qemu/' + plan['cc_dockerfile'] + '\' container)'
endif
endif
tcg_tests_summary += {'cc for ' + target: summary_cc}
folder = plan['folder']
gdb_arch = plan['gdb_arch']
qemu = plan['qemu']
cc_feat_cflags = {}
if 'cc_feat_cflags' in plan
cc_feat_cflags = plan['cc_feat_cflags']
endif
gdb_feat_version = {}
if 'gdb_feat_version' in plan
gdb_feat_version = plan['gdb_feat_version']
endif
tests = plan['tests']
foreach t : tests
foreach src, setup: t
# return a clear error if user misspell a setup entry
foreach key, _ : setup
allowed = ['exe_name', 'env_var', 'expected_output', 'cc_feat', 'cflags',
'depends', 'gdb_feat', 'gdb_test', 'plugin_test', 'qemu_args',
'test_name', 'wrapper']
if key not in allowed
error('unknown tcg setup entry \'' + key + '\' for test ' + src +
' (possible: [' + ', '.join(allowed) + '])')
endif
endforeach
# return a clear error if user misspell a plugin entry
if 'plugin_test' in setup
foreach key, _ : setup['plugin_test']
allowed = ['args', 'plugin']
if key not in allowed
error('unknown tcg plugin entry \'' + key + '\' for test ' + src +
' (possible: [' + ', '.join(allowed) + '])')
endif
endforeach
endif
# if set, we'll skip test and print the reason
skip_test = ''
if 'plugin_test' in setup and not get_option('plugins')
# do not use the skip_test logic, and simply ignore the test
continue
endif
# meson '/' operator drops left operand if right is an absolute path
src = folder / src
file = files(src)
exe_name = fs.stem(src)
if 'exe_name' in setup
exe_name = setup['exe_name']
endif
test_name = exe_name
if 'test_name' in setup
test_name = setup['test_name']
endif
if 'gdb_test' in setup
test_name = 'gdb-' + test_name
elif 'plugin_test' in setup
plugin = test_plugins[setup['plugin_test']['plugin']]
# remove 'lib' prefix
plugin_name = fs.stem(plugin).substring(3)
test_name = 'plugin-' + plugin_name + '-with-' + test_name
endif
exe_name = target + '-' + exe_name
test_name = target + '-' + test_name
if test_name in added_tests
error('test ' + test_name + ' was already added: ' +
'specify a different \'test_name\'')
endif
if 'cflags' in setup or 'cc_feat' in setup
if exe_name in built_tests
error('test ' + exe_name +
' was already built with cflags or cc_feat:' +
' remove cflags, cc_feat, or set \'exe_name\'' +
' (if cflags are different)')
endif
endif
cflags = cc_cflags
if 'cflags' in setup
cflags += setup['cflags']
endif
if 'cc_feat' in setup
cc_feat = setup['cc_feat']
if cc_feat not in cc_has_feat
avail = false
if cc_from_system
# check flag for host cc
cmd = run_command([cc, '-xc', '/dev/null', '-o', '/dev/null', '-c',
cc_feat_cflags[cc_feat]],
check: false)
avail = cmd.returncode() == 0
elif has_cc
# docker cc always have feature
avail = true
endif
cc_has_feat += {cc_feat: avail}
endif
if not cc_has_feat[cc_feat]
skip_test = 'compiler feature ' + cc_feat + ' not supported'
built_tests += {exe_name: exe_name}
endif
cflags += cc_feat_cflags[cc_feat]
endif
if not has_cc
skip_test = 'compiler ' + plan['cc'] + ' not available'
built_tests += {exe_name: exe_name}
endif
exe_depends = build_test_depends
if 'depends' in setup
exe_depends += setup['depends']
endif
# build executable if needed
if exe_name not in built_tests
exe = custom_target(exe_name,
input: file,
command: [cc, '@INPUT@',
'-o', '@OUTPUT@', '-static',
'-MMD', '-MF', '@DEPFILE@',
'-Wall', '-Werror',
'-O0', '-g', '-fno-strict-aliasing',
# Some host compilers (like archlinux)
# have stack protection enabled by default.
# This results in a linking error for some
# of our tests due to missing symbol
# __stack_chk_fail. Thus, disable it.
'-fno-stack-protector',
cflags],
depfile: exe_name + '.d',
output: exe_name + '.test',
depends: exe_depends,
depend_files: build_test_depend_files,
build_by_default: false)
exe_targets += exe
built_tests += {exe_name: exe}
endif
test_depends = []
qemu_args = []
if 'qemu_args' in setup
qemu_args = setup['qemu_args']
endif
if 'plugin_test' in setup
plugin_setup = setup['plugin_test']
plugin = test_plugins[plugin_setup['plugin']]
plugin_args = []
if 'args' in plugin_setup
# add empty arg to generate an additional comma
plugin_args = ['', plugin_setup['args']]
endif
plugin_args = ','.join(plugin_args)
# since we turn plugin into a string, add dependency explicitly
test_depends += plugin
qemu_args = ['-plugin', plugin.full_path() + plugin_args, qemu_args]
endif
exe = built_tests[exe_name]
test_depends += qemu
cmd = [qemu.full_path(), qemu_args, exe]
if 'gdb_test' in setup
cmd = [prog_gdb_test,
'--gdb', gdb,
'--qemu', qemu.full_path(),
'--qargs', ' '.join(qemu_args),
'--bin', exe,
setup['gdb_test'],
]
endif
if 'expected_output' in setup
expected = files(folder / setup['expected_output'])
cmd = [prog_run_and_diff, expected, cmd]
endif
if 'wrapper' in setup
cmd = [setup['wrapper'], cmd]
endif
# some tests expect QEMU env var to be set
cmd = ['QEMU=' + qemu.full_path(), cmd]
if 'env_var' in setup
cmd = [setup['env_var'], cmd]
endif
if 'gdb_feat' in setup
if 'gdb_test' not in setup
error('test ' + test_name + ' is not a gdb test, but it requires ' +
'a gdb feature')
endif
gdb_feat = setup['gdb_feat']
if not gdb_version.version_compare('>=' + gdb_feat_version[gdb_feat])
skip_test = 'gdb feature ' + gdb_feat + ' not supported'
endif
endif
if 'gdb_test' in setup and gdb_arch not in gdb_arch_supported
skip_test = 'gdb not available for ' + gdb_arch
endif
if skip_test != ''
# still declare test, but skip it and print reason
cmd = ['sh', '-c', 'echo "' + skip_test + '" && exit 77']
endif
# Do not add a skipped test when compiler is missing,
# though we still check all tests are correctly defined.
if has_cc
test(test_name, env, args: cmd,
depends: test_depends,
timeout: 120,
suite: ['tcg', 'tcg-' + target])
endif
added_tests += {test_name: true}
endforeach
endforeach
endforeach
if exe_targets.length() > 0
# finally create a top level target to rebuild all tests
alias_target('tcg-tests', exe_targets)
# before, this target was called build-tcg. Keep it for compatibility sake.
alias_target('build-tcg', exe_targets)
endif