mirror of
https://github.com/memtest86plus/memtest86plus.git
synced 2026-07-31 08:38:30 -05:00
341 lines
11 KiB
C
341 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// Copyright (C) 2004-2026 Sam Demeulemeester.
|
|
//
|
|
// Vector-wide pseudo-random sequence primitives shared by the memory tests.
|
|
//
|
|
// Memory is filled and checked with per-lane xorshift streams, one whole
|
|
// vector block at a time. On x86_64 the work is dispatched at runtime to
|
|
// AVX2 or SSE2 kernels with non-temporal stores (tests/x86/vec_prsg_*.c);
|
|
// other builds use the scalar kernels below, which implement the same
|
|
// algorithm. The check functions regenerate the sequence instead of storing
|
|
// it: xorshift is an invertible linear map, so the descending check simply
|
|
// steps the lane states backwards.
|
|
//
|
|
// SIMD dispatch approach inspired by earlier experimental work by
|
|
// Lionel Debroux.
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
#include "simd.h"
|
|
|
|
#include "error.h"
|
|
#include "test.h"
|
|
|
|
#include "test_helper.h"
|
|
|
|
#include "vec_prsg.h"
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Constants
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Arbitrary distinct constants used to decorrelate the per-lane xorshift
|
|
// streams seeded from a single per-round seed.
|
|
#if TESTWORD_WIDTH > 32
|
|
static const testword_t lane_salt[VEC_LANES] = {
|
|
UINT64_C(0x9e3779b97f4a7c15),
|
|
UINT64_C(0xbf58476d1ce4e5b9),
|
|
UINT64_C(0x94d049bb133111eb),
|
|
UINT64_C(0x2545f4914f6cdd1d)
|
|
};
|
|
#else
|
|
static const testword_t lane_salt[VEC_LANES] = {
|
|
0x9e3779b9,
|
|
0xbf58476d,
|
|
0x94d049bb,
|
|
0x2545f491
|
|
};
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Private Functions
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Inverts one y = x ^ (x << shift) step of the xorshift generator by repeated
|
|
// squaring, doubling the shift until it exceeds the word width.
|
|
static inline testword_t prsg_undo_lshift(testword_t y, int shift)
|
|
{
|
|
for (int k = shift; k < TESTWORD_WIDTH; k *= 2) {
|
|
y ^= y << k;
|
|
}
|
|
return y;
|
|
}
|
|
|
|
// Inverts one y = x ^ (x >> shift) step of the xorshift generator by repeated
|
|
// squaring, doubling the shift until it exceeds the word width.
|
|
static inline testword_t prsg_undo_rshift(testword_t y, int shift)
|
|
{
|
|
for (int k = shift; k < TESTWORD_WIDTH; k *= 2) {
|
|
y ^= y >> k;
|
|
}
|
|
return y;
|
|
}
|
|
|
|
// Returns the previous word in the pseudo-random sequence generated by prsg().
|
|
static inline testword_t prsg_inverse(testword_t state)
|
|
{
|
|
#if TESTWORD_WIDTH > 32
|
|
state = prsg_undo_lshift(state, 17);
|
|
state = prsg_undo_rshift(state, 7);
|
|
state = prsg_undo_lshift(state, 13);
|
|
#else
|
|
state = prsg_undo_lshift(state, 5);
|
|
state = prsg_undo_rshift(state, 17);
|
|
state = prsg_undo_lshift(state, 13);
|
|
#endif
|
|
return state;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Scalar kernels (all architectures)
|
|
//
|
|
// These process nblocks whole vector blocks starting at p (the descending
|
|
// check processes them from the top down). The lane states are carried in
|
|
// memory between invocations, so no SIMD state is live across calls to
|
|
// data_error(), do_tick(), or the thread barriers.
|
|
//------------------------------------------------------------------------------
|
|
|
|
static void vec_fill_scalar(vec_state_t *st, testword_t *p, size_t nblocks, bool splat)
|
|
{
|
|
for (size_t k = 0; k < nblocks; k++) {
|
|
for (int l = 0; l < VEC_LANES; l++) {
|
|
write_word(&p[l], st->lane[l]);
|
|
}
|
|
p += VEC_LANES;
|
|
if (!splat) {
|
|
for (int l = 0; l < VEC_LANES; l++) {
|
|
st->lane[l] = prsg(st->lane[l]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void vec_check_fwd_scalar(vec_state_t *st, testword_t *p, size_t nblocks, bool splat, bool use_for_badram)
|
|
{
|
|
for (size_t k = 0; k < nblocks; k++) {
|
|
for (int l = 0; l < VEC_LANES; l++) {
|
|
testword_t expect = st->lane[l];
|
|
testword_t actual = read_word(&p[l]);
|
|
if (unlikely(actual != expect)) {
|
|
data_error(&p[l], expect, actual, use_for_badram);
|
|
}
|
|
write_word(&p[l], ~expect);
|
|
}
|
|
p += VEC_LANES;
|
|
if (!splat) {
|
|
for (int l = 0; l < VEC_LANES; l++) {
|
|
st->lane[l] = prsg(st->lane[l]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void vec_check_rev_scalar(vec_state_t *st, testword_t *p, size_t nblocks, bool splat, bool use_for_badram)
|
|
{
|
|
for (size_t k = nblocks; k-- > 0; ) {
|
|
testword_t *q = p + k * VEC_LANES;
|
|
if (!splat) {
|
|
for (int l = 0; l < VEC_LANES; l++) {
|
|
st->lane[l] = prsg_inverse(st->lane[l]);
|
|
}
|
|
}
|
|
for (int l = 0; l < VEC_LANES; l++) {
|
|
testword_t expect = ~st->lane[l];
|
|
testword_t actual = read_word(&q[l]);
|
|
if (unlikely(actual != expect)) {
|
|
data_error(&q[l], expect, actual, use_for_badram);
|
|
}
|
|
write_word(&q[l], st->lane[l]);
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Public Functions
|
|
//------------------------------------------------------------------------------
|
|
|
|
void seed_lanes(vec_state_t *st, testword_t seed, bool splat)
|
|
{
|
|
if (splat) {
|
|
testword_t s = seed != 0 ? seed : lane_salt[0];
|
|
s = prsg(s);
|
|
for (int l = 0; l < VEC_LANES; l++) {
|
|
st->lane[l] = s;
|
|
}
|
|
} else {
|
|
for (int l = 0; l < VEC_LANES; l++) {
|
|
testword_t s = seed ^ lane_salt[l];
|
|
if (s == 0) {
|
|
s = lane_salt[l];
|
|
}
|
|
st->lane[l] = prsg(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
void vec_fill(vec_state_t *st, testword_t *p, size_t nblocks, bool splat)
|
|
{
|
|
#if defined(__x86_64__)
|
|
if (simd_tier == SIMD_AVX2) {
|
|
vec_fill_avx2(st, p, nblocks, splat);
|
|
return;
|
|
}
|
|
if (simd_tier == SIMD_SSE2) {
|
|
vec_fill_sse2(st, p, nblocks, splat);
|
|
return;
|
|
}
|
|
#elif defined(__aarch64__)
|
|
if (simd_tier == SIMD_NEON) {
|
|
vec_fill_neon(st, p, nblocks, splat);
|
|
return;
|
|
}
|
|
#elif defined(__loongarch_lp64)
|
|
if (simd_tier == SIMD_LASX) {
|
|
vec_fill_lasx(st, p, nblocks, splat);
|
|
return;
|
|
}
|
|
#endif
|
|
vec_fill_scalar(st, p, nblocks, splat);
|
|
}
|
|
|
|
void vec_check_fwd(vec_state_t *st, testword_t *p, size_t nblocks, bool splat, bool use_for_badram)
|
|
{
|
|
#if defined(__x86_64__)
|
|
if (simd_tier != SIMD_NONE) {
|
|
while (nblocks > 0) {
|
|
size_t done = (simd_tier == SIMD_AVX2) ? vec_scan_fwd_avx2(st, p, nblocks, splat)
|
|
: vec_scan_fwd_sse2(st, p, nblocks, splat);
|
|
p += done * VEC_LANES;
|
|
nblocks -= done;
|
|
if (nblocks == 0) {
|
|
break;
|
|
}
|
|
// The scan stopped at a mismatching block. Report and fix it up,
|
|
// then resume the scan on the remaining blocks.
|
|
vec_check_fwd_scalar(st, p, 1, splat, use_for_badram);
|
|
p += VEC_LANES;
|
|
nblocks--;
|
|
}
|
|
return;
|
|
}
|
|
#elif defined(__aarch64__)
|
|
if (simd_tier == SIMD_NEON) {
|
|
while (nblocks > 0) {
|
|
size_t done = vec_scan_fwd_neon(st, p, nblocks, splat);
|
|
p += done * VEC_LANES;
|
|
nblocks -= done;
|
|
if (nblocks == 0) {
|
|
break;
|
|
}
|
|
// The scan stopped at a mismatching block. Report and fix it up,
|
|
// then resume the scan on the remaining blocks.
|
|
vec_check_fwd_scalar(st, p, 1, splat, use_for_badram);
|
|
p += VEC_LANES;
|
|
nblocks--;
|
|
}
|
|
return;
|
|
}
|
|
#elif defined(__loongarch_lp64)
|
|
if (simd_tier == SIMD_LASX) {
|
|
while (nblocks > 0) {
|
|
size_t done = vec_scan_fwd_lasx(st, p, nblocks, splat);
|
|
p += done * VEC_LANES;
|
|
nblocks -= done;
|
|
if (nblocks == 0) {
|
|
break;
|
|
}
|
|
// The scan stopped at a mismatching block. Report and fix it up,
|
|
// then resume the scan on the remaining blocks.
|
|
vec_check_fwd_scalar(st, p, 1, splat, use_for_badram);
|
|
p += VEC_LANES;
|
|
nblocks--;
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
vec_check_fwd_scalar(st, p, nblocks, splat, use_for_badram);
|
|
}
|
|
|
|
void vec_check_rev(vec_state_t *st, testword_t *p, size_t nblocks, bool splat, bool use_for_badram)
|
|
{
|
|
#if defined(__x86_64__)
|
|
if (simd_tier != SIMD_NONE) {
|
|
testword_t *q = p + (nblocks - 1) * VEC_LANES;
|
|
while (nblocks > 0) {
|
|
size_t done = (simd_tier == SIMD_AVX2) ? vec_scan_rev_avx2(st, q, nblocks, splat)
|
|
: vec_scan_rev_sse2(st, q, nblocks, splat);
|
|
q -= done * VEC_LANES;
|
|
nblocks -= done;
|
|
if (nblocks == 0) {
|
|
break;
|
|
}
|
|
// The scan stopped at a mismatching block, with the lane states
|
|
// already stepped back to it. Report and fix it up, then resume.
|
|
// Unlike the forward case, the scalar kernel cannot be reused
|
|
// here as it would step the lane states back a second time.
|
|
for (int l = 0; l < VEC_LANES; l++) {
|
|
testword_t expect = ~st->lane[l];
|
|
testword_t actual = read_word(&q[l]);
|
|
if (unlikely(actual != expect)) {
|
|
data_error(&q[l], expect, actual, use_for_badram);
|
|
}
|
|
write_word(&q[l], st->lane[l]);
|
|
}
|
|
q -= VEC_LANES;
|
|
nblocks--;
|
|
}
|
|
return;
|
|
}
|
|
#elif defined(__aarch64__)
|
|
if (simd_tier == SIMD_NEON) {
|
|
testword_t *q = p + (nblocks - 1) * VEC_LANES;
|
|
while (nblocks > 0) {
|
|
size_t done = vec_scan_rev_neon(st, q, nblocks, splat);
|
|
q -= done * VEC_LANES;
|
|
nblocks -= done;
|
|
if (nblocks == 0) {
|
|
break;
|
|
}
|
|
// The scan stopped at a mismatching block.
|
|
for (int l = 0; l < VEC_LANES; l++) {
|
|
testword_t expect = ~st->lane[l];
|
|
testword_t actual = read_word(&q[l]);
|
|
if (unlikely(actual != expect)) {
|
|
data_error(&q[l], expect, actual, use_for_badram);
|
|
}
|
|
write_word(&q[l], st->lane[l]);
|
|
}
|
|
q -= VEC_LANES;
|
|
nblocks--;
|
|
}
|
|
return;
|
|
}
|
|
#elif defined(__loongarch_lp64)
|
|
if (simd_tier == SIMD_LASX) {
|
|
testword_t *q = p + (nblocks - 1) * VEC_LANES;
|
|
while (nblocks > 0) {
|
|
size_t done = vec_scan_rev_lasx(st, q, nblocks, splat);
|
|
q -= done * VEC_LANES;
|
|
nblocks -= done;
|
|
if (nblocks == 0) {
|
|
break;
|
|
}
|
|
// The scan stopped at a mismatching block.
|
|
for (int l = 0; l < VEC_LANES; l++) {
|
|
testword_t expect = ~st->lane[l];
|
|
testword_t actual = read_word(&q[l]);
|
|
if (unlikely(actual != expect)) {
|
|
data_error(&q[l], expect, actual, use_for_badram);
|
|
}
|
|
write_word(&q[l], st->lane[l]);
|
|
}
|
|
q -= VEC_LANES;
|
|
nblocks--;
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
vec_check_rev_scalar(st, p, nblocks, splat, use_for_badram);
|
|
}
|