Commit f2ad736f63 for qemu.org
commit f2ad736f63a62f00bd99398fca0ef2c8d4cd411a
Author: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Date: Thu Oct 29 15:13:24 2026 +0100
tests/tcg/mips: Add MSA load/store tests
Add coverage for the MSA LD/ST.B/H/W/D instructions at every source
and destination byte offset within a vector-sized block, including
accesses that cross a page boundary. Verify per-element endian
ordering, destination contents, and source register preservation.
Test both little-endian (P5600, MIPS32R5) and big-endian
(I6400, MIPS64R6).
AI-used-for: tests
Signed-off-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
ERROR: use qemu_real_host_page_size() instead of sysconf(_SC_PAGESIZE)
#138: FILE: tests/tcg/mips/test_msa_load_store.c:110:
+ long page_size = sysconf(_SC_PAGESIZE);
Message-ID: <20261030175357.99684-33-philmd@oss.qualcomm.com>
diff --git a/tests/tcg/mips/meson.build b/tests/tcg/mips/meson.build
index 560d07ad63..1795025045 100644
--- a/tests/tcg/mips/meson.build
+++ b/tests/tcg/mips/meson.build
@@ -16,6 +16,7 @@ tests_mips_msa_groups = [
'../mips/test_msa_int_multiply.c',
'../mips/test_msa_int_subtract.c',
'../mips/test_msa_interleave.c',
+ '../mips/test_msa_load_store.c',
'../mips/test_msa_logic.c',
'../mips/test_msa_pack.c',
'../mips/test_msa_shift.c',
diff --git a/tests/tcg/mips/test_msa_load_store.c b/tests/tcg/mips/test_msa_load_store.c
new file mode 100644
index 0000000000..e026833a54
--- /dev/null
+++ b/tests/tcg/mips/test_msa_load_store.c
@@ -0,0 +1,171 @@
+/*
+ * MIPS MSA tests: Load / Store instructions
+ *
+ * Author:
+ * Philippe Mathieu-Daudé
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "../include/tap.h"
+
+enum {
+ VECTOR_BYTES = 16,
+ BUFFER_BYTES = 64,
+};
+
+#define DEFINE_MSA_COPY(format) \
+static void copy_##format(const uint8_t *source, uint8_t *destination, \
+ uint8_t *loaded, uint8_t *after_store) \
+{ \
+ __asm__ volatile( \
+ "ld." #format " $w0, 0(%[source])\n\t" \
+ "st.b $w0, 0(%[loaded])\n\t" \
+ "st." #format " $w0, 0(%[destination])\n\t" \
+ "st.b $w0, 0(%[after_store])\n\t" \
+ : \
+ : [source] "r" (source), [destination] "r" (destination), \
+ [loaded] "r" (loaded), [after_store] "r" (after_store) \
+ : "f0", "memory"); \
+}
+
+DEFINE_MSA_COPY(b)
+DEFINE_MSA_COPY(h)
+DEFINE_MSA_COPY(w)
+DEFINE_MSA_COPY(d)
+
+typedef void (*MsaCopy)(const uint8_t *source, uint8_t *destination,
+ uint8_t *loaded, uint8_t *after_store);
+
+static const struct {
+ const char *name;
+ unsigned element_bytes;
+ MsaCopy copy;
+} formats[] = {
+ { "b", 1, copy_b },
+ { "h", 2, copy_h },
+ { "w", 4, copy_w },
+ { "d", 8, copy_d },
+};
+
+static bool check_format(size_t format_index, const uint8_t *source,
+ uint8_t *destination, size_t first_offset,
+ const char *region)
+{
+ _Alignas(VECTOR_BYTES) uint8_t loaded[VECTOR_BYTES];
+ _Alignas(VECTOR_BYTES) uint8_t after_store[VECTOR_BYTES];
+ uint8_t expected_vector[VECTOR_BYTES];
+ uint8_t expected_memory[BUFFER_BYTES];
+ const uint16_t endian_probe = 1;
+ unsigned endian_mask = *(const uint8_t *)&endian_probe ? 0 :
+ formats[format_index].element_bytes - 1;
+
+ for (size_t source_offset = first_offset;
+ source_offset < first_offset + VECTOR_BYTES; source_offset++) {
+ for (size_t destination_offset = first_offset;
+ destination_offset < first_offset + VECTOR_BYTES;
+ destination_offset++) {
+ memset(destination, 0xa5, BUFFER_BYTES);
+ memset(expected_memory, 0xa5, BUFFER_BYTES);
+ memcpy(expected_memory + destination_offset,
+ source + source_offset, VECTOR_BYTES);
+
+ for (size_t byte = 0; byte < VECTOR_BYTES; byte++) {
+ expected_vector[byte] =
+ source[source_offset + (byte ^ endian_mask)];
+ }
+
+ formats[format_index].copy(source + source_offset,
+ destination + destination_offset,
+ loaded, after_store);
+
+ if (memcmp(loaded, expected_vector, VECTOR_BYTES)) {
+ TAP_INFO("ld.%s endian mismatch (%s, offset %zu)",
+ formats[format_index].name, region, source_offset);
+ return false;
+ }
+ if (memcmp(destination, expected_memory, BUFFER_BYTES)) {
+ TAP_INFO("st.%s memory mismatch (%s, offset %zu)",
+ formats[format_index].name, region,
+ destination_offset);
+ return false;
+ }
+ if (memcmp(after_store, loaded, VECTOR_BYTES)) {
+ TAP_INFO("st.%s changed its source register (%s)",
+ formats[format_index].name, region);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+int main(void)
+{
+ _Alignas(VECTOR_BYTES) uint8_t source[BUFFER_BYTES];
+ _Alignas(VECTOR_BYTES) uint8_t destination[BUFFER_BYTES];
+ long page_size = sysconf(_SC_PAGESIZE);
+ size_t mapping_bytes;
+ uint8_t *source_mapping;
+ uint8_t *destination_mapping;
+ uint8_t *page_source;
+ uint8_t *page_destination;
+
+ TAP_START();
+ if (page_size < BUFFER_BYTES) {
+ TAP_INFO("Invalid guest page size: %ld", page_size);
+ tap_report(0, "MSA ld/st guest page size");
+ return tap_done();
+ }
+ mapping_bytes = 4 * (size_t)page_size;
+ source_mapping = mmap(NULL, mapping_bytes, PROT_NONE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ destination_mapping = mmap(NULL, mapping_bytes, PROT_NONE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (source_mapping == MAP_FAILED || destination_mapping == MAP_FAILED) {
+ TAP_INFO("mmap: %s", strerror(errno));
+ tap_report(0, "MSA ld/st mmap");
+ return tap_done();
+ }
+ if (mprotect(source_mapping + page_size, 2 * page_size,
+ PROT_READ | PROT_WRITE) != 0 ||
+ mprotect(destination_mapping + page_size, 2 * page_size,
+ PROT_READ | PROT_WRITE) != 0) {
+ TAP_INFO("mprotect: %s", strerror(errno));
+ tap_report(0, "MSA ld/st mprotect");
+ return tap_done();
+ }
+
+ page_source = source_mapping + 2 * page_size - BUFFER_BYTES / 2;
+ page_destination = destination_mapping + 2 * page_size - BUFFER_BYTES / 2;
+ for (size_t byte = 0; byte < BUFFER_BYTES; byte++) {
+ source[byte] = page_source[byte] = byte * 13 + 7;
+ }
+
+ for (size_t format_index = 0;
+ format_index < sizeof(formats) / sizeof(formats[0]); format_index++) {
+ bool passed = check_format(format_index, source, destination, 0,
+ "buffer") &&
+ check_format(format_index, page_source, page_destination,
+ VECTOR_BYTES, "page boundary");
+
+ tap_report(passed, "MSA ld/st.%s", formats[format_index].name);
+ }
+
+ if (munmap(source_mapping, mapping_bytes) != 0 ||
+ munmap(destination_mapping, mapping_bytes) != 0) {
+ TAP_INFO("munmap: %s", strerror(errno));
+ tap_report(0, "MSA ld/st munmap");
+ return tap_done();
+ }
+ return tap_done();
+}