From 31bb1e8e0b9a82cf03d90d9931fc5ee551a141f6 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Wed, 7 Jun 2023 01:01:59 +0300 Subject: [PATCH 001/103] Add Xbyak submodule --- .gitmodules | 3 +++ CMakeLists.txt | 6 +++++- include/PICA/dynapica/pica_recs.hpp | 6 ++++++ third_party/xbyak | 1 + 4 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 include/PICA/dynapica/pica_recs.hpp create mode 160000 third_party/xbyak diff --git a/.gitmodules b/.gitmodules index ac10e7f3..4305ba4d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "third_party/cryptopp/cryptopp"] path = third_party/cryptopp/cryptopp url = https://github.com/weidai11/cryptopp +[submodule "third_party/xbyak"] + path = third_party/xbyak + url = https://github.com/herumi/xbyak diff --git a/CMakeLists.txt b/CMakeLists.txt index 44cbd873..169cedcf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,10 @@ add_subdirectory(third_party/cryptopp) # Check for x64 if (CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86-64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") set(HOST_X64 TRUE) + add_subdirectory(third_party/xbyak) # Add xbyak submodule for x86 JITs + include_directories(third_party/xbyak/xbyak) + add_compile_definitions(PANDA3DS_DYNAPICA_SUPPORTED) + add_compile_definitions(PANDA3DS_X64_HOST) else() set(HOST_X64 FALSE) endif() @@ -114,7 +118,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp inc include/fs/archive_ext_save_data.hpp include/services/shared_font.hpp include/fs/archive_ncch.hpp include/renderer_gl/textures.hpp include/colour.hpp include/services/y2r.hpp include/services/cam.hpp include/services/ldr_ro.hpp include/ipc.hpp include/services/act.hpp include/services/nfc.hpp - include/system_models.hpp include/services/dlp_srvr.hpp + include/system_models.hpp include/services/dlp_srvr.hpp include/PICA/dynapica/pica_recs.hpp ) set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp diff --git a/include/PICA/dynapica/pica_recs.hpp b/include/PICA/dynapica/pica_recs.hpp new file mode 100644 index 00000000..89daae3a --- /dev/null +++ b/include/PICA/dynapica/pica_recs.hpp @@ -0,0 +1,6 @@ +#pragma once +#include "helpers.hpp" + +namespace Dynapica { + +} \ No newline at end of file diff --git a/third_party/xbyak b/third_party/xbyak new file mode 160000 index 00000000..ad5276fa --- /dev/null +++ b/third_party/xbyak @@ -0,0 +1 @@ +Subproject commit ad5276fa4d86d9579ff626719cea43c809a6cab8 From 9ac8817ee3e44e46c13c179a5649a6d3d14f8779 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Wed, 7 Jun 2023 02:16:22 +0300 Subject: [PATCH 002/103] More vertex loader JIT files --- CMakeLists.txt | 3 +- include/PICA/dynapica/pica_recs.hpp | 9 ++++- include/PICA/dynapica/vertex_loader_rec.hpp | 0 include/PICA/dynapica/x64_regs.hpp | 39 +++++++++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 include/PICA/dynapica/vertex_loader_rec.hpp create mode 100644 include/PICA/dynapica/x64_regs.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 169cedcf..c6dd4c53 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,7 @@ add_subdirectory(third_party/cryptopp) if (CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86-64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") set(HOST_X64 TRUE) add_subdirectory(third_party/xbyak) # Add xbyak submodule for x86 JITs - include_directories(third_party/xbyak/xbyak) + include_directories(third_party/xbyak) add_compile_definitions(PANDA3DS_DYNAPICA_SUPPORTED) add_compile_definitions(PANDA3DS_X64_HOST) else() @@ -119,6 +119,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp inc include/renderer_gl/textures.hpp include/colour.hpp include/services/y2r.hpp include/services/cam.hpp include/services/ldr_ro.hpp include/ipc.hpp include/services/act.hpp include/services/nfc.hpp include/system_models.hpp include/services/dlp_srvr.hpp include/PICA/dynapica/pica_recs.hpp + include/PICA/dynapica/x64_regs.hpp include/PICA/dynapica/vertex_loader_rec.hpp ) set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp diff --git a/include/PICA/dynapica/pica_recs.hpp b/include/PICA/dynapica/pica_recs.hpp index 89daae3a..acfd226e 100644 --- a/include/PICA/dynapica/pica_recs.hpp +++ b/include/PICA/dynapica/pica_recs.hpp @@ -1,6 +1,13 @@ #pragma once #include "helpers.hpp" +#include "vertex_loader_rec.hpp" + +// Common file for our PICA JITs (From vertex config -> CPU assembly and from PICA shader -> CPU assembly) namespace Dynapica { - +#ifdef PANDA3DS_DYNAPICA_SUPPORTED + static constexpr bool supported() { return true; } +#else + static constexpr bool supported() { return false; } +#endif } \ No newline at end of file diff --git a/include/PICA/dynapica/vertex_loader_rec.hpp b/include/PICA/dynapica/vertex_loader_rec.hpp new file mode 100644 index 00000000..e69de29b diff --git a/include/PICA/dynapica/x64_regs.hpp b/include/PICA/dynapica/x64_regs.hpp new file mode 100644 index 00000000..47bcec7d --- /dev/null +++ b/include/PICA/dynapica/x64_regs.hpp @@ -0,0 +1,39 @@ +#pragma once + +#ifdef PANDA3DS_X64_HOST +#include "xbyak/xbyak.h" +using namespace Xbyak; +using namespace Xbyak::util; + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) +constexpr Reg32 arg1 = ecx; // register where first arg is stored +constexpr Reg32 arg2 = edx; // register where second arg is stored +constexpr Reg32 arg3 = r8d; // register where third arg is stored +constexpr Reg32 arg4 = r9d; // register where fourth arg is stored + +// Similar for floating point and vector arguemnts. +constexpr Xmm arg1f = xmm0; +constexpr Xmm arg2f = xmm1; +constexpr Xmm arg3f = xmm2; +constexpr Xmm arg4f = xmm3; + +constexpr bool isWindows() { return true; } + +#else // System V calling convention +constexpr Reg32 arg1 = edi; +constexpr Reg32 arg2 = esi; +constexpr Reg32 arg3 = edx; +constexpr Reg32 arg4 = ecx; + +constexpr Xmm arg1f = xmm0; +constexpr Xmm arg2f = xmm1; +constexpr Xmm arg3f = xmm2; +constexpr Xmm arg4f = xmm3; +constexpr Xmm arg5f = xmm4; +constexpr Xmm arg6f = xmm5; +constexpr Xmm arg7f = xmm6; +constexpr Xmm arg8f = xmm7; + +constexpr bool isWindows() { return false; } +#endif +#endif // PANDA3DS_X64_HOST \ No newline at end of file From 4541841da54a4ea364a951e254f1aa10684e81cf Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Wed, 7 Jun 2023 02:57:00 +0300 Subject: [PATCH 003/103] More boilerplate --- include/PICA/dynapica/vertex_loader_rec.hpp | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/include/PICA/dynapica/vertex_loader_rec.hpp b/include/PICA/dynapica/vertex_loader_rec.hpp index e69de29b..856fcc9f 100644 --- a/include/PICA/dynapica/vertex_loader_rec.hpp +++ b/include/PICA/dynapica/vertex_loader_rec.hpp @@ -0,0 +1,22 @@ +#pragma once +#include "helpers.hpp" +#include "renderer_gl/renderer_gl.hpp" +#include "x64_regs.hpp" + +// Recompiler that takes the current vertex attribute configuration, ie the format of vertices (VAO in OpenGL) and emits optimized +// code in our CPU's native architecture for loading vertices + +class VertexLoaderJIT { + using PICARegs = const std::array&; + using Callback = void(*)(Vertex* output, size_t count); // A function pointer to JIT-emitted code + Callback compileConfig(PICARegs regs); + +public: +#ifndef PANDA3DS_DYNAPICA_SUPPORTED + void loadVertices(Vertex* output, size_t count, PICARegs regs) { + Helpers::panic("Vertex Loader JIT: Tried to load vertices with JIT on platform that does not support vertex loader jit"); + } +#else + void loadVertices(Vertex* output, size_t count, PICARegs regs); +#endif +}; \ No newline at end of file From bf0436a85beddbb10af28daeba2008fbf331e45c Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Wed, 7 Jun 2023 16:59:46 +0300 Subject: [PATCH 004/103] Add CityHash --- CMakeLists.txt | 2 + third_party/cityhash/cityhash.cpp | 340 ++++++++++++++ third_party/cityhash/cityhash.hpp | 111 +++++ third_party/cityhash/swap.hpp | 718 ++++++++++++++++++++++++++++++ 4 files changed, 1171 insertions(+) create mode 100644 third_party/cityhash/cityhash.cpp create mode 100644 third_party/cityhash/cityhash.hpp create mode 100644 third_party/cityhash/swap.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c6dd4c53..369ae0e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,6 +128,8 @@ set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp third_party/imgui/imgui_widgets.cpp third_party/imgui/imgui_demo.cpp third_party/gl3w/gl3w.cpp + + third_party/cityhash/cityhash.cpp ) source_group("Header Files\\Core" FILES ${HEADER_FILES}) diff --git a/third_party/cityhash/cityhash.cpp b/third_party/cityhash/cityhash.cpp new file mode 100644 index 00000000..a9d5406b --- /dev/null +++ b/third_party/cityhash/cityhash.cpp @@ -0,0 +1,340 @@ +// Copyright (c) 2011 Google, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// CityHash, by Geoff Pike and Jyrki Alakuijala +// +// This file provides CityHash64() and related functions. +// +// It's probably possible to create even faster hash functions by +// writing a program that systematically explores some of the space of +// possible hash functions, by using SIMD instructions, or by +// compromising on hash quality. + +#include +#include // for memcpy and memset +#include "cityhash.hpp" +#include "swap.hpp" + +// #include "config.h" +#ifdef __GNUC__ +#define HAVE_BUILTIN_EXPECT 1 +#endif +#ifdef COMMON_BIG_ENDIAN +#define WORDS_BIGENDIAN 1 +#endif + +using namespace std; + +typedef uint8_t uint8; +typedef uint32_t uint32; +typedef uint64_t uint64; + +namespace CityHash { + +static uint64 UNALIGNED_LOAD64(const char* p) { + uint64 result; + memcpy(&result, p, sizeof(result)); + return result; +} + +static uint32 UNALIGNED_LOAD32(const char* p) { + uint32 result; + memcpy(&result, p, sizeof(result)); + return result; +} + +#ifdef WORDS_BIGENDIAN +#define uint32_in_expected_order(x) (swap32(x)) +#define uint64_in_expected_order(x) (swap64(x)) +#else +#define uint32_in_expected_order(x) (x) +#define uint64_in_expected_order(x) (x) +#endif + +#if !defined(LIKELY) +#if HAVE_BUILTIN_EXPECT +#define LIKELY(x) (__builtin_expect(!!(x), 1)) +#else +#define LIKELY(x) (x) +#endif +#endif + +static uint64 Fetch64(const char* p) { + return uint64_in_expected_order(UNALIGNED_LOAD64(p)); +} + +static uint32 Fetch32(const char* p) { + return uint32_in_expected_order(UNALIGNED_LOAD32(p)); +} + +// Some primes between 2^63 and 2^64 for various uses. +static const uint64 k0 = 0xc3a5c85c97cb3127ULL; +static const uint64 k1 = 0xb492b66fbe98f273ULL; +static const uint64 k2 = 0x9ae16a3b2f90404fULL; + +// Bitwise right rotate. Normally this will compile to a single +// instruction, especially if the shift is a manifest constant. +static uint64 Rotate(uint64 val, int shift) { + // Avoid shifting by 64: doing so yields an undefined result. + return shift == 0 ? val : ((val >> shift) | (val << (64 - shift))); +} + +static uint64 ShiftMix(uint64 val) { + return val ^ (val >> 47); +} + +static uint64 HashLen16(uint64 u, uint64 v) { + return Hash128to64(uint128(u, v)); +} + +static uint64 HashLen16(uint64 u, uint64 v, uint64 mul) { + // Murmur-inspired hashing. + uint64 a = (u ^ v) * mul; + a ^= (a >> 47); + uint64 b = (v ^ a) * mul; + b ^= (b >> 47); + b *= mul; + return b; +} + +static uint64 HashLen0to16(const char* s, std::size_t len) { + if (len >= 8) { + uint64 mul = k2 + len * 2; + uint64 a = Fetch64(s) + k2; + uint64 b = Fetch64(s + len - 8); + uint64 c = Rotate(b, 37) * mul + a; + uint64 d = (Rotate(a, 25) + b) * mul; + return HashLen16(c, d, mul); + } + if (len >= 4) { + uint64 mul = k2 + len * 2; + uint64 a = Fetch32(s); + return HashLen16(len + (a << 3), Fetch32(s + len - 4), mul); + } + if (len > 0) { + uint8 a = s[0]; + uint8 b = s[len >> 1]; + uint8 c = s[len - 1]; + uint32 y = static_cast(a) + (static_cast(b) << 8); + uint32 z = static_cast(len) + (static_cast(c) << 2); + return ShiftMix(y * k2 ^ z * k0) * k2; + } + return k2; +} + +// This probably works well for 16-byte strings as well, but it may be overkill +// in that case. +static uint64 HashLen17to32(const char* s, std::size_t len) { + uint64 mul = k2 + len * 2; + uint64 a = Fetch64(s) * k1; + uint64 b = Fetch64(s + 8); + uint64 c = Fetch64(s + len - 8) * mul; + uint64 d = Fetch64(s + len - 16) * k2; + return HashLen16(Rotate(a + b, 43) + Rotate(c, 30) + d, a + Rotate(b + k2, 18) + c, mul); +} + +// Return a 16-byte hash for 48 bytes. Quick and dirty. +// Callers do best to use "random-looking" values for a and b. +static pair WeakHashLen32WithSeeds(uint64 w, uint64 x, uint64 y, uint64 z, uint64 a, + uint64 b) { + a += w; + b = Rotate(b + a + z, 21); + uint64 c = a; + a += x; + a += y; + b += Rotate(a, 44); + return make_pair(a + z, b + c); +} + +// Return a 16-byte hash for s[0] ... s[31], a, and b. Quick and dirty. +static pair WeakHashLen32WithSeeds(const char* s, uint64 a, uint64 b) { + return WeakHashLen32WithSeeds(Fetch64(s), Fetch64(s + 8), Fetch64(s + 16), Fetch64(s + 24), a, + b); +} + +// Return an 8-byte hash for 33 to 64 bytes. +static uint64 HashLen33to64(const char* s, std::size_t len) { + uint64 mul = k2 + len * 2; + uint64 a = Fetch64(s) * k2; + uint64 b = Fetch64(s + 8); + uint64 c = Fetch64(s + len - 24); + uint64 d = Fetch64(s + len - 32); + uint64 e = Fetch64(s + 16) * k2; + uint64 f = Fetch64(s + 24) * 9; + uint64 g = Fetch64(s + len - 8); + uint64 h = Fetch64(s + len - 16) * mul; + uint64 u = Rotate(a + g, 43) + (Rotate(b, 30) + c) * 9; + uint64 v = ((a + g) ^ d) + f + 1; + uint64 w = Common::swap64((u + v) * mul) + h; + uint64 x = Rotate(e + f, 42) + c; + uint64 y = (Common::swap64((v + w) * mul) + g) * mul; + uint64 z = e + f + c; + a = Common::swap64((x + z) * mul + y) + b; + b = ShiftMix((z + a) * mul + d + h) * mul; + return b + x; +} + +uint64 CityHash64(const char* s, std::size_t len) { + if (len <= 32) { + if (len <= 16) { + return HashLen0to16(s, len); + } else { + return HashLen17to32(s, len); + } + } else if (len <= 64) { + return HashLen33to64(s, len); + } + + // For strings over 64 bytes we hash the end first, and then as we + // loop we keep 56 bytes of state: v, w, x, y, and z. + uint64 x = Fetch64(s + len - 40); + uint64 y = Fetch64(s + len - 16) + Fetch64(s + len - 56); + uint64 z = HashLen16(Fetch64(s + len - 48) + len, Fetch64(s + len - 24)); + pair v = WeakHashLen32WithSeeds(s + len - 64, len, z); + pair w = WeakHashLen32WithSeeds(s + len - 32, y + k1, x); + x = x * k1 + Fetch64(s); + + // Decrease len to the nearest multiple of 64, and operate on 64-byte chunks. + len = (len - 1) & ~static_cast(63); + do { + x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; + y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; + x ^= w.second; + y += v.first + Fetch64(s + 40); + z = Rotate(z + w.first, 33) * k1; + v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); + w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); + std::swap(z, x); + s += 64; + len -= 64; + } while (len != 0); + return HashLen16(HashLen16(v.first, w.first) + ShiftMix(y) * k1 + z, + HashLen16(v.second, w.second) + x); +} + +uint64 CityHash64WithSeed(const char* s, std::size_t len, uint64 seed) { + return CityHash64WithSeeds(s, len, k2, seed); +} + +uint64 CityHash64WithSeeds(const char* s, std::size_t len, uint64 seed0, uint64 seed1) { + return HashLen16(CityHash64(s, len) - seed0, seed1); +} + +// A subroutine for CityHash128(). Returns a decent 128-bit hash for strings +// of any length representable in signed long. Based on City and Murmur. +static uint128 CityMurmur(const char* s, std::size_t len, uint128 seed) { + uint64 a = Uint128Low64(seed); + uint64 b = Uint128High64(seed); + uint64 c = 0; + uint64 d = 0; + signed long l = static_cast(len) - 16; + if (l <= 0) { // len <= 16 + a = ShiftMix(a * k1) * k1; + c = b * k1 + HashLen0to16(s, len); + d = ShiftMix(a + (len >= 8 ? Fetch64(s) : c)); + } else { // len > 16 + c = HashLen16(Fetch64(s + len - 8) + k1, a); + d = HashLen16(b + len, c + Fetch64(s + len - 16)); + a += d; + do { + a ^= ShiftMix(Fetch64(s) * k1) * k1; + a *= k1; + b ^= a; + c ^= ShiftMix(Fetch64(s + 8) * k1) * k1; + c *= k1; + d ^= c; + s += 16; + l -= 16; + } while (l > 0); + } + a = HashLen16(a, c); + b = HashLen16(d, b); + return uint128(a ^ b, HashLen16(b, a)); +} + +uint128 CityHash128WithSeed(const char* s, std::size_t len, uint128 seed) { + if (len < 128) { + return CityMurmur(s, len, seed); + } + + // We expect len >= 128 to be the common case. Keep 56 bytes of state: + // v, w, x, y, and z. + pair v, w; + uint64 x = Uint128Low64(seed); + uint64 y = Uint128High64(seed); + uint64 z = len * k1; + v.first = Rotate(y ^ k1, 49) * k1 + Fetch64(s); + v.second = Rotate(v.first, 42) * k1 + Fetch64(s + 8); + w.first = Rotate(y + z, 35) * k1 + x; + w.second = Rotate(x + Fetch64(s + 88), 53) * k1; + + // This is the same inner loop as CityHash64(), manually unrolled. + do { + x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; + y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; + x ^= w.second; + y += v.first + Fetch64(s + 40); + z = Rotate(z + w.first, 33) * k1; + v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); + w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); + std::swap(z, x); + s += 64; + x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; + y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; + x ^= w.second; + y += v.first + Fetch64(s + 40); + z = Rotate(z + w.first, 33) * k1; + v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); + w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); + std::swap(z, x); + s += 64; + len -= 128; + } while (LIKELY(len >= 128)); + x += Rotate(v.first + z, 49) * k0; + y = y * k0 + Rotate(w.second, 37); + z = z * k0 + Rotate(w.first, 27); + w.first *= 9; + v.first *= k0; + // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. + for (std::size_t tail_done = 0; tail_done < len;) { + tail_done += 32; + y = Rotate(x + y, 42) * k0 + v.second; + w.first += Fetch64(s + len - tail_done + 16); + x = x * k0 + w.first; + z += w.second + Fetch64(s + len - tail_done); + w.second += v.first; + v = WeakHashLen32WithSeeds(s + len - tail_done, v.first + z, v.second); + v.first *= k0; + } + // At this point our 56 bytes of state should contain more than + // enough information for a strong 128-bit hash. We use two + // different 56-byte-to-8-byte hashes to get a 16-byte final result. + x = HashLen16(x, v.first); + y = HashLen16(y + z, w.first); + return uint128(HashLen16(x + v.second, w.second) + y, HashLen16(x + w.second, y + v.second)); +} + +uint128 CityHash128(const char* s, std::size_t len) { + return len >= 16 + ? CityHash128WithSeed(s + 16, len - 16, uint128(Fetch64(s), Fetch64(s + 8) + k0)) + : CityHash128WithSeed(s, len, uint128(k0, k1)); +} + +} // namespace CityHash \ No newline at end of file diff --git a/third_party/cityhash/cityhash.hpp b/third_party/cityhash/cityhash.hpp new file mode 100644 index 00000000..c27bb887 --- /dev/null +++ b/third_party/cityhash/cityhash.hpp @@ -0,0 +1,111 @@ +// Copyright (c) 2011 Google, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// CityHash, by Geoff Pike and Jyrki Alakuijala +// +// http://code.google.com/p/cityhash/ +// +// This file provides a few functions for hashing strings. All of them are +// high-quality functions in the sense that they pass standard tests such +// as Austin Appleby's SMHasher. They are also fast. +// +// For 64-bit x86 code, on short strings, we don't know of anything faster than +// CityHash64 that is of comparable quality. We believe our nearest competitor +// is Murmur3. For 64-bit x86 code, CityHash64 is an excellent choice for hash +// tables and most other hashing (excluding cryptography). +// +// For 64-bit x86 code, on long strings, the picture is more complicated. +// On many recent Intel CPUs, such as Nehalem, Westmere, Sandy Bridge, etc., +// CityHashCrc128 appears to be faster than all competitors of comparable +// quality. CityHash128 is also good but not quite as fast. We believe our +// nearest competitor is Bob Jenkins' Spooky. We don't have great data for +// other 64-bit CPUs, but for long strings we know that Spooky is slightly +// faster than CityHash on some relatively recent AMD x86-64 CPUs, for example. +// Note that CityHashCrc128 is declared in citycrc.h. +// +// For 32-bit x86 code, we don't know of anything faster than CityHash32 that +// is of comparable quality. We believe our nearest competitor is Murmur3A. +// (On 64-bit CPUs, it is typically faster to use the other CityHash variants.) +// +// Functions in the CityHash family are not suitable for cryptography. +// +// Please see CityHash's README file for more details on our performance +// measurements and so on. +// +// WARNING: This code has been only lightly tested on big-endian platforms! +// It is known to work well on little-endian platforms that have a small penalty +// for unaligned reads, such as current Intel and AMD moderate-to-high-end CPUs. +// It should work on all 32-bit and 64-bit platforms that allow unaligned reads; +// bug reports are welcome. +// +// By the way, for some hash functions, given strings a and b, the hash +// of a+b is easily derived from the hashes of a and b. This property +// doesn't hold for any hash functions in this file. + +#pragma once + +#include +#include +#include + +namespace CityHash { + +using uint128 = std::pair; + +[[nodiscard]] inline uint64_t Uint128Low64(const uint128& x) { + return x.first; +} +[[nodiscard]] inline uint64_t Uint128High64(const uint128& x) { + return x.second; +} + +// Hash function for a byte array. +[[nodiscard]] uint64_t CityHash64(const char* buf, std::size_t len); + +// Hash function for a byte array. For convenience, a 64-bit seed is also +// hashed into the result. +[[nodiscard]] uint64_t CityHash64WithSeed(const char* buf, std::size_t len, uint64_t seed); + +// Hash function for a byte array. For convenience, two seeds are also +// hashed into the result. +[[nodiscard]] uint64_t CityHash64WithSeeds(const char* buf, std::size_t len, uint64_t seed0, + uint64_t seed1); + +// Hash function for a byte array. +[[nodiscard]] uint128 CityHash128(const char* s, std::size_t len); + +// Hash function for a byte array. For convenience, a 128-bit seed is also +// hashed into the result. +[[nodiscard]] uint128 CityHash128WithSeed(const char* s, std::size_t len, uint128 seed); + +// Hash 128 input bits down to 64 bits of output. +// This is intended to be a reasonably good hash function. +[[nodiscard]] inline uint64_t Hash128to64(const uint128& x) { + // Murmur-inspired hashing. + const uint64_t kMul = 0x9ddfea08eb382d69ULL; + uint64_t a = (Uint128Low64(x) ^ Uint128High64(x)) * kMul; + a ^= (a >> 47); + uint64_t b = (Uint128High64(x) ^ a) * kMul; + b ^= (b >> 47); + b *= kMul; + return b; +} + +} // namespace CityHash \ No newline at end of file diff --git a/third_party/cityhash/swap.hpp b/third_party/cityhash/swap.hpp new file mode 100644 index 00000000..b7db9226 --- /dev/null +++ b/third_party/cityhash/swap.hpp @@ -0,0 +1,718 @@ +// Copyright (c) 2012- PPSSPP Project / Dolphin Project. + +// 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, version 2.0 or later versions. + +// 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 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include + +#if defined(_MSC_VER) +#include +#endif +#include +#include "helpers.hpp" + +// GCC +#ifdef __GNUC__ + +#if __BYTE_ORDER__ && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) && !defined(COMMON_LITTLE_ENDIAN) +#define COMMON_LITTLE_ENDIAN 1 +#elif __BYTE_ORDER__ && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) && !defined(COMMON_BIG_ENDIAN) +#define COMMON_BIG_ENDIAN 1 +#endif + +// LLVM/clang +#elif defined(__clang__) + +#if __LITTLE_ENDIAN__ && !defined(COMMON_LITTLE_ENDIAN) +#define COMMON_LITTLE_ENDIAN 1 +#elif __BIG_ENDIAN__ && !defined(COMMON_BIG_ENDIAN) +#define COMMON_BIG_ENDIAN 1 +#endif + +// MSVC +#elif defined(_MSC_VER) && !defined(COMMON_BIG_ENDIAN) && !defined(COMMON_LITTLE_ENDIAN) + +#define COMMON_LITTLE_ENDIAN 1 +#endif + +// Worst case, default to little endian. +#if !COMMON_BIG_ENDIAN && !COMMON_LITTLE_ENDIAN +#define COMMON_LITTLE_ENDIAN 1 +#endif + +namespace Common { + +#ifdef _MSC_VER +[[nodiscard]] inline u16 swap16(u16 data) noexcept { + return _byteswap_ushort(data); +} +[[nodiscard]] inline u32 swap32(u32 data) noexcept { + return _byteswap_ulong(data); +} +[[nodiscard]] inline u64 swap64(u64 data) noexcept { + return _byteswap_uint64(data); +} +#elif defined(__clang__) || defined(__GNUC__) +#if defined(__Bitrig__) || defined(__OpenBSD__) +// redefine swap16, swap32, swap64 as inline functions +#undef swap16 +#undef swap32 +#undef swap64 +#endif +[[nodiscard]] inline u16 swap16(u16 data) noexcept { + return __builtin_bswap16(data); +} +[[nodiscard]] inline u32 swap32(u32 data) noexcept { + return __builtin_bswap32(data); +} +[[nodiscard]] inline u64 swap64(u64 data) noexcept { + return __builtin_bswap64(data); +} +#else +// Generic implementation. +[[nodiscard]] inline u16 swap16(u16 data) noexcept { + return (data >> 8) | (data << 8); +} +[[nodiscard]] inline u32 swap32(u32 data) noexcept { + return ((data & 0xFF000000U) >> 24) | ((data & 0x00FF0000U) >> 8) | + ((data & 0x0000FF00U) << 8) | ((data & 0x000000FFU) << 24); +} +[[nodiscard]] inline u64 swap64(u64 data) noexcept { + return ((data & 0xFF00000000000000ULL) >> 56) | ((data & 0x00FF000000000000ULL) >> 40) | + ((data & 0x0000FF0000000000ULL) >> 24) | ((data & 0x000000FF00000000ULL) >> 8) | + ((data & 0x00000000FF000000ULL) << 8) | ((data & 0x0000000000FF0000ULL) << 24) | + ((data & 0x000000000000FF00ULL) << 40) | ((data & 0x00000000000000FFULL) << 56); +} +#endif + +[[nodiscard]] inline float swapf(float f) noexcept { + static_assert(sizeof(u32) == sizeof(float), "float must be the same size as uint32_t."); + + u32 value; + std::memcpy(&value, &f, sizeof(u32)); + + value = swap32(value); + std::memcpy(&f, &value, sizeof(u32)); + + return f; +} + +[[nodiscard]] inline double swapd(double f) noexcept { + static_assert(sizeof(u64) == sizeof(double), "double must be the same size as uint64_t."); + + u64 value; + std::memcpy(&value, &f, sizeof(u64)); + + value = swap64(value); + std::memcpy(&f, &value, sizeof(u64)); + + return f; +} + +} // Namespace Common + +template +struct swap_struct_t { + using swapped_t = swap_struct_t; + +protected: + T value; + + static T swap(T v) { + return F::swap(v); + } + +public: + T swap() const { + return swap(value); + } + swap_struct_t() = default; + swap_struct_t(const T& v) : value(swap(v)) {} + + template + swapped_t& operator=(const S& source) { + value = swap(static_cast(source)); + return *this; + } + + operator s8() const { + return static_cast(swap()); + } + operator u8() const { + return static_cast(swap()); + } + operator s16() const { + return static_cast(swap()); + } + operator u16() const { + return static_cast(swap()); + } + operator s32() const { + return static_cast(swap()); + } + operator u32() const { + return static_cast(swap()); + } + operator s64() const { + return static_cast(swap()); + } + operator u64() const { + return static_cast(swap()); + } + operator float() const { + return static_cast(swap()); + } + operator double() const { + return static_cast(swap()); + } + + // +v + swapped_t operator+() const { + return +swap(); + } + // -v + swapped_t operator-() const { + return -swap(); + } + + // v / 5 + swapped_t operator/(const swapped_t& i) const { + return swap() / i.swap(); + } + template + swapped_t operator/(const S& i) const { + return swap() / i; + } + + // v * 5 + swapped_t operator*(const swapped_t& i) const { + return swap() * i.swap(); + } + template + swapped_t operator*(const S& i) const { + return swap() * i; + } + + // v + 5 + swapped_t operator+(const swapped_t& i) const { + return swap() + i.swap(); + } + template + swapped_t operator+(const S& i) const { + return swap() + static_cast(i); + } + // v - 5 + swapped_t operator-(const swapped_t& i) const { + return swap() - i.swap(); + } + template + swapped_t operator-(const S& i) const { + return swap() - static_cast(i); + } + + // v += 5 + swapped_t& operator+=(const swapped_t& i) { + value = swap(swap() + i.swap()); + return *this; + } + template + swapped_t& operator+=(const S& i) { + value = swap(swap() + static_cast(i)); + return *this; + } + // v -= 5 + swapped_t& operator-=(const swapped_t& i) { + value = swap(swap() - i.swap()); + return *this; + } + template + swapped_t& operator-=(const S& i) { + value = swap(swap() - static_cast(i)); + return *this; + } + + // ++v + swapped_t& operator++() { + value = swap(swap() + 1); + return *this; + } + // --v + swapped_t& operator--() { + value = swap(swap() - 1); + return *this; + } + + // v++ + swapped_t operator++(int) { + swapped_t old = *this; + value = swap(swap() + 1); + return old; + } + // v-- + swapped_t operator--(int) { + swapped_t old = *this; + value = swap(swap() - 1); + return old; + } + // Comparaison + // v == i + bool operator==(const swapped_t& i) const { + return swap() == i.swap(); + } + template + bool operator==(const S& i) const { + return swap() == i; + } + + // v != i + bool operator!=(const swapped_t& i) const { + return swap() != i.swap(); + } + template + bool operator!=(const S& i) const { + return swap() != i; + } + + // v > i + bool operator>(const swapped_t& i) const { + return swap() > i.swap(); + } + template + bool operator>(const S& i) const { + return swap() > i; + } + + // v < i + bool operator<(const swapped_t& i) const { + return swap() < i.swap(); + } + template + bool operator<(const S& i) const { + return swap() < i; + } + + // v >= i + bool operator>=(const swapped_t& i) const { + return swap() >= i.swap(); + } + template + bool operator>=(const S& i) const { + return swap() >= i; + } + + // v <= i + bool operator<=(const swapped_t& i) const { + return swap() <= i.swap(); + } + template + bool operator<=(const S& i) const { + return swap() <= i; + } + + // logical + swapped_t operator!() const { + return !swap(); + } + + // bitmath + swapped_t operator~() const { + return ~swap(); + } + + swapped_t operator&(const swapped_t& b) const { + return swap() & b.swap(); + } + template + swapped_t operator&(const S& b) const { + return swap() & b; + } + swapped_t& operator&=(const swapped_t& b) { + value = swap(swap() & b.swap()); + return *this; + } + template + swapped_t& operator&=(const S b) { + value = swap(swap() & b); + return *this; + } + + swapped_t operator|(const swapped_t& b) const { + return swap() | b.swap(); + } + template + swapped_t operator|(const S& b) const { + return swap() | b; + } + swapped_t& operator|=(const swapped_t& b) { + value = swap(swap() | b.swap()); + return *this; + } + template + swapped_t& operator|=(const S& b) { + value = swap(swap() | b); + return *this; + } + + swapped_t operator^(const swapped_t& b) const { + return swap() ^ b.swap(); + } + template + swapped_t operator^(const S& b) const { + return swap() ^ b; + } + swapped_t& operator^=(const swapped_t& b) { + value = swap(swap() ^ b.swap()); + return *this; + } + template + swapped_t& operator^=(const S& b) { + value = swap(swap() ^ b); + return *this; + } + + template + swapped_t operator<<(const S& b) const { + return swap() << b; + } + template + swapped_t& operator<<=(const S& b) const { + value = swap(swap() << b); + return *this; + } + + template + swapped_t operator>>(const S& b) const { + return swap() >> b; + } + template + swapped_t& operator>>=(const S& b) const { + value = swap(swap() >> b); + return *this; + } + + // Member + /** todo **/ + + // Arithmetics + template + friend S operator+(const S& p, const swapped_t v); + + template + friend S operator-(const S& p, const swapped_t v); + + template + friend S operator/(const S& p, const swapped_t v); + + template + friend S operator*(const S& p, const swapped_t v); + + template + friend S operator%(const S& p, const swapped_t v); + + // Arithmetics + assignments + template + friend S operator+=(const S& p, const swapped_t v); + + template + friend S operator-=(const S& p, const swapped_t v); + + // Bitmath + template + friend S operator&(const S& p, const swapped_t v); + + // Comparison + template + friend bool operator<(const S& p, const swapped_t v); + + template + friend bool operator>(const S& p, const swapped_t v); + + template + friend bool operator<=(const S& p, const swapped_t v); + + template + friend bool operator>=(const S& p, const swapped_t v); + + template + friend bool operator!=(const S& p, const swapped_t v); + + template + friend bool operator==(const S& p, const swapped_t v); +}; + +// Arithmetics +template +S operator+(const S& i, const swap_struct_t v) { + return i + v.swap(); +} + +template +S operator-(const S& i, const swap_struct_t v) { + return i - v.swap(); +} + +template +S operator/(const S& i, const swap_struct_t v) { + return i / v.swap(); +} + +template +S operator*(const S& i, const swap_struct_t v) { + return i * v.swap(); +} + +template +S operator%(const S& i, const swap_struct_t v) { + return i % v.swap(); +} + +// Arithmetics + assignments +template +S& operator+=(S& i, const swap_struct_t v) { + i += v.swap(); + return i; +} + +template +S& operator-=(S& i, const swap_struct_t v) { + i -= v.swap(); + return i; +} + +// Logical +template +S operator&(const S& i, const swap_struct_t v) { + return i & v.swap(); +} + +template +S operator&(const swap_struct_t v, const S& i) { + return static_cast(v.swap() & i); +} + +// Comparaison +template +bool operator<(const S& p, const swap_struct_t v) { + return p < v.swap(); +} +template +bool operator>(const S& p, const swap_struct_t v) { + return p > v.swap(); +} +template +bool operator<=(const S& p, const swap_struct_t v) { + return p <= v.swap(); +} +template +bool operator>=(const S& p, const swap_struct_t v) { + return p >= v.swap(); +} +template +bool operator!=(const S& p, const swap_struct_t v) { + return p != v.swap(); +} +template +bool operator==(const S& p, const swap_struct_t v) { + return p == v.swap(); +} + +template +struct swap_64_t { + static T swap(T x) { + return static_cast(Common::swap64(x)); + } +}; + +template +struct swap_32_t { + static T swap(T x) { + return static_cast(Common::swap32(x)); + } +}; + +template +struct swap_16_t { + static T swap(T x) { + return static_cast(Common::swap16(x)); + } +}; + +template +struct swap_float_t { + static T swap(T x) { + return static_cast(Common::swapf(x)); + } +}; + +template +struct swap_double_t { + static T swap(T x) { + return static_cast(Common::swapd(x)); + } +}; + +template +struct swap_enum_t { + static_assert(std::is_enum_v); + using base = std::underlying_type_t; + +public: + swap_enum_t() = default; + swap_enum_t(const T& v) : value(swap(v)) {} + + swap_enum_t& operator=(const T& v) { + value = swap(v); + return *this; + } + + operator T() const { + return swap(value); + } + + explicit operator base() const { + return static_cast(swap(value)); + } + +protected: + T value{}; + // clang-format off + using swap_t = std::conditional_t< + std::is_same_v, swap_16_t, std::conditional_t< + std::is_same_v, swap_16_t, std::conditional_t< + std::is_same_v, swap_32_t, std::conditional_t< + std::is_same_v, swap_32_t, std::conditional_t< + std::is_same_v, swap_64_t, std::conditional_t< + std::is_same_v, swap_64_t, void>>>>>>; + // clang-format on + static T swap(T x) { + return static_cast(swap_t::swap(static_cast(x))); + } +}; + +struct SwapTag {}; // Use the different endianness from the system +struct KeepTag {}; // Use the same endianness as the system + +template +struct AddEndian; + +// KeepTag specializations + +template +struct AddEndian { + using type = T; +}; + +// SwapTag specializations + +template <> +struct AddEndian { + using type = u8; +}; + +template <> +struct AddEndian { + using type = swap_struct_t>; +}; + +template <> +struct AddEndian { + using type = swap_struct_t>; +}; + +template <> +struct AddEndian { + using type = swap_struct_t>; +}; + +template <> +struct AddEndian { + using type = s8; +}; + +template <> +struct AddEndian { + using type = swap_struct_t>; +}; + +template <> +struct AddEndian { + using type = swap_struct_t>; +}; + +template <> +struct AddEndian { + using type = swap_struct_t>; +}; + +template <> +struct AddEndian { + using type = swap_struct_t>; +}; + +template <> +struct AddEndian { + using type = swap_struct_t>; +}; + +template +struct AddEndian { + static_assert(std::is_enum_v); + using type = swap_enum_t; +}; + +// Alias LETag/BETag as KeepTag/SwapTag depending on the system +#if COMMON_LITTLE_ENDIAN + +using LETag = KeepTag; +using BETag = SwapTag; + +#else + +using BETag = KeepTag; +using LETag = SwapTag; + +#endif + +// Aliases for LE types +using u16_le = AddEndian::type; +using u32_le = AddEndian::type; +using u64_le = AddEndian::type; + +using s16_le = AddEndian::type; +using s32_le = AddEndian::type; +using s64_le = AddEndian::type; + +template +using enum_le = std::enable_if_t, typename AddEndian::type>; + +using float_le = AddEndian::type; +using double_le = AddEndian::type; + +// Aliases for BE types +using u16_be = AddEndian::type; +using u32_be = AddEndian::type; +using u64_be = AddEndian::type; + +using s16_be = AddEndian::type; +using s32_be = AddEndian::type; +using s64_be = AddEndian::type; + +template +using enum_be = std::enable_if_t, typename AddEndian::type>; + +using float_be = AddEndian::type; +using double_be = AddEndian::type; \ No newline at end of file From cf9ed3d46073ec264275e3465429b96e0ea18e13 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Wed, 7 Jun 2023 17:44:46 +0300 Subject: [PATCH 005/103] More shader JIT groundwork --- CMakeLists.txt | 6 ++++-- include/PICA/dynapica/shader_rec.hpp | 21 +++++++++++++++++++ include/PICA/dynapica/vertex_loader_rec.hpp | 10 ++++++--- include/PICA/gpu.hpp | 1 + include/PICA/shader.hpp | 8 +++++-- src/core/PICA/dynapica/shader_rec.cpp | 7 +++++++ .../dynapica/shader_rec_instructions_x64.cpp | 0 7 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 include/PICA/dynapica/shader_rec.hpp create mode 100644 src/core/PICA/dynapica/shader_rec.cpp create mode 100644 src/core/PICA/dynapica/shader_rec_instructions_x64.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 369ae0e4..f3e80c04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,8 +91,10 @@ set(SERVICE_SOURCE_FILES src/core/services/service_manager.cpp src/core/services src/core/services/act.cpp src/core/services/nfc.cpp src/core/services/dlp_srvr.cpp ) set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp src/core/PICA/regs.cpp src/core/PICA/shader_unit.cpp - src/core/PICA/shader_interpreter.cpp + src/core/PICA/shader_interpreter.cpp src/core/PICA/dynapica/shader_rec.cpp + src/core/PICA/dynapica/shader_rec_instructions_x64.cpp ) + set(RENDERER_GL_SOURCE_FILES src/core/renderer_gl/renderer_gl.cpp src/core/renderer_gl/textures.cpp src/core/renderer_gl/etc1.cpp) set(LOADER_SOURCE_FILES src/core/loader/elf.cpp src/core/loader/ncsd.cpp src/core/loader/ncch.cpp src/core/loader/lz77.cpp) @@ -119,7 +121,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp inc include/renderer_gl/textures.hpp include/colour.hpp include/services/y2r.hpp include/services/cam.hpp include/services/ldr_ro.hpp include/ipc.hpp include/services/act.hpp include/services/nfc.hpp include/system_models.hpp include/services/dlp_srvr.hpp include/PICA/dynapica/pica_recs.hpp - include/PICA/dynapica/x64_regs.hpp include/PICA/dynapica/vertex_loader_rec.hpp + include/PICA/dynapica/x64_regs.hpp include/PICA/dynapica/vertex_loader_rec.hpp include/PICA/dynapica/shader_rec.hpp ) set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp diff --git a/include/PICA/dynapica/shader_rec.hpp b/include/PICA/dynapica/shader_rec.hpp new file mode 100644 index 00000000..9a17e905 --- /dev/null +++ b/include/PICA/dynapica/shader_rec.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "PICA/shader.hpp" + +class ShaderJIT { + void compileShader(PICAShader& shaderUnit); + +public: +#if defined(PANDA3DS_DYNAPICA_SUPPORTED) && defined(PANDA3DS_X64_HOST) + #define PANDA3DS_SHADER_JIT_SUPPORTED + + // Call this before starting to process a batch of vertices + // This will read the PICA config (uploaded shader and shader operand descriptors) and search if we've already compiled this shader + // If yes, it sets it as the active shader. if not, then it compiles it, adds it to the cache, and sets it as active, + void prepare(PICAShader& shaderUnit); +#else + void prepare(PICAShader& shaderUnit) { + Helpers::panic("Vertex Loader JIT: Tried to load vertices with JIT on platform that does not support vertex loader jit"); + } +#endif + +}; \ No newline at end of file diff --git a/include/PICA/dynapica/vertex_loader_rec.hpp b/include/PICA/dynapica/vertex_loader_rec.hpp index 856fcc9f..71be1ca9 100644 --- a/include/PICA/dynapica/vertex_loader_rec.hpp +++ b/include/PICA/dynapica/vertex_loader_rec.hpp @@ -12,11 +12,15 @@ class VertexLoaderJIT { Callback compileConfig(PICARegs regs); public: -#ifndef PANDA3DS_DYNAPICA_SUPPORTED +#if defined(PANDA3DS_DYNAPICA_SUPPORTED) && defined(PANDA3DS_X64_HOST) + void loadVertices(Vertex* output, size_t count, PICARegs regs); + static constexpr bool isAvailable() { return true; } + +#else void loadVertices(Vertex* output, size_t count, PICARegs regs) { Helpers::panic("Vertex Loader JIT: Tried to load vertices with JIT on platform that does not support vertex loader jit"); } -#else - void loadVertices(Vertex* output, size_t count, PICARegs regs); + + static constexpr bool isAvailable() { return false; } #endif }; \ No newline at end of file diff --git a/include/PICA/gpu.hpp b/include/PICA/gpu.hpp index 4b60b3ef..284e5a28 100644 --- a/include/PICA/gpu.hpp +++ b/include/PICA/gpu.hpp @@ -6,6 +6,7 @@ #include "PICA/float_types.hpp" #include "PICA/regs.hpp" #include "PICA/shader_unit.hpp" +#include "PICA/dynapica/shader_rec.hpp" #include "renderer_gl/renderer_gl.hpp" class GPU { diff --git a/include/PICA/shader.hpp b/include/PICA/shader.hpp index 8c0ca499..65b8da10 100644 --- a/include/PICA/shader.hpp +++ b/include/PICA/shader.hpp @@ -71,6 +71,8 @@ class PICAShader { bool f32UniformTransfer = false; // Are we transferring an f32 uniform or an f24 uniform? std::array floatUniformBuffer; // Buffer for temporarily caching float uniform data + +protected: std::array operandDescriptors; std::array tempRegisters; // General purpose registers the shader can use for temp values OpenGL::Vector addrRegister; // Address register @@ -85,13 +87,15 @@ class PICAShader { std::array loopInfo; std::array conditionalInfo; std::array callInfo; - ShaderType type; + friend class ShaderJIT; + +private: vec4f getSource(u32 source); vec4f& getDest(u32 dest); - // Shader opcodes + // Interpreter functions for the various shader functions void add(u32 instruction); void call(u32 instruction); void callc(u32 instruction); diff --git a/src/core/PICA/dynapica/shader_rec.cpp b/src/core/PICA/dynapica/shader_rec.cpp new file mode 100644 index 00000000..8f04d1f2 --- /dev/null +++ b/src/core/PICA/dynapica/shader_rec.cpp @@ -0,0 +1,7 @@ +#include "PICA/dynapica/shader_rec.hpp" + +#ifdef PANDA3DS_SHADER_JIT_SUPPORTED +void ShaderJIT::prepare(PICAShader& shaderUnit) { + printf("HAPPY HAPPY HAPPY\n"); +} +#endif // PANDA3DS_SHADER_JIT_SUPPORTED \ No newline at end of file diff --git a/src/core/PICA/dynapica/shader_rec_instructions_x64.cpp b/src/core/PICA/dynapica/shader_rec_instructions_x64.cpp new file mode 100644 index 00000000..e69de29b From 364443d66f89393eeaf9e10c9582a79cfe8122c2 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Wed, 7 Jun 2023 18:22:07 +0300 Subject: [PATCH 006/103] Moar --- include/PICA/dynapica/shader_rec.hpp | 2 ++ include/PICA/dynapica/vertex_loader_rec.hpp | 2 ++ include/PICA/gpu.hpp | 2 +- src/core/PICA/gpu.cpp | 19 ++++++++++++++----- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/include/PICA/dynapica/shader_rec.hpp b/include/PICA/dynapica/shader_rec.hpp index 9a17e905..48104364 100644 --- a/include/PICA/dynapica/shader_rec.hpp +++ b/include/PICA/dynapica/shader_rec.hpp @@ -12,10 +12,12 @@ public: // This will read the PICA config (uploaded shader and shader operand descriptors) and search if we've already compiled this shader // If yes, it sets it as the active shader. if not, then it compiles it, adds it to the cache, and sets it as active, void prepare(PICAShader& shaderUnit); + static constexpr bool isAvailable() { return true; } #else void prepare(PICAShader& shaderUnit) { Helpers::panic("Vertex Loader JIT: Tried to load vertices with JIT on platform that does not support vertex loader jit"); } + static constexpr bool isAvailable() { return false; } #endif }; \ No newline at end of file diff --git a/include/PICA/dynapica/vertex_loader_rec.hpp b/include/PICA/dynapica/vertex_loader_rec.hpp index 71be1ca9..b515a4f7 100644 --- a/include/PICA/dynapica/vertex_loader_rec.hpp +++ b/include/PICA/dynapica/vertex_loader_rec.hpp @@ -13,6 +13,8 @@ class VertexLoaderJIT { public: #if defined(PANDA3DS_DYNAPICA_SUPPORTED) && defined(PANDA3DS_X64_HOST) + #define PANDA3DS_VERTEX_LOADER_JIT_SUPPORTED + void loadVertices(Vertex* output, size_t count, PICARegs regs); static constexpr bool isAvailable() { return true; } diff --git a/include/PICA/gpu.hpp b/include/PICA/gpu.hpp index 284e5a28..347dd946 100644 --- a/include/PICA/gpu.hpp +++ b/include/PICA/gpu.hpp @@ -29,7 +29,7 @@ class GPU { uint immediateModeVertIndex; uint immediateModeAttrIndex; // Index of the immediate mode attribute we're uploading - template + template void drawArrays(); // Silly method of avoiding linking problems. TODO: Change to something less silly diff --git a/src/core/PICA/gpu.cpp b/src/core/PICA/gpu.cpp index 7e9ddfea..e11b07a2 100644 --- a/src/core/PICA/gpu.cpp +++ b/src/core/PICA/gpu.cpp @@ -33,16 +33,25 @@ void GPU::reset() { renderer.reset(); } +// Call the correct version of drawArrays based on whether this is an indexed draw (first template parameter) +// And whether we are going to use the shader JIT (second template parameter) void GPU::drawArrays(bool indexed) { - if (indexed) - drawArrays(); - else - drawArrays(); + if (indexed) { + if constexpr (ShaderJIT::isAvailable()) + drawArrays(); + else + drawArrays(); + } else { + if constexpr (ShaderJIT::isAvailable()) + drawArrays(); + else + drawArrays(); + } } Vertex* vertices = new Vertex[Renderer::vertexBufferSize]; -template +template void GPU::drawArrays() { // Base address for vertex attributes // The vertex base is always on a quadword boundary because the PICA does weird alignment shit any time possible From 4064abfdebac1cb2da213fc12b1b240a99136d9c Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Wed, 7 Jun 2023 20:33:37 +0300 Subject: [PATCH 007/103] [Shader JIT] Add caching --- CMakeLists.txt | 1 + include/PICA/dynapica/shader_rec.hpp | 19 +++++++++++--- include/PICA/gpu.hpp | 2 ++ include/PICA/shader.hpp | 18 +++++++++++++ src/core/PICA/dynapica/shader_rec.cpp | 9 ++++++- src/core/PICA/gpu.cpp | 5 ++++ src/core/PICA/shader_unit.cpp | 26 +++++++++++++++++++ third_party/cityhash/cityhash.cpp | 2 +- .../cityhash/{ => include}/cityhash.hpp | 0 9 files changed, 77 insertions(+), 5 deletions(-) rename third_party/cityhash/{ => include}/cityhash.hpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index f3e80c04..95ea1066 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ include_directories(third_party/gl3w/) include_directories(third_party/imgui/) include_directories(third_party/dynarmic/src) include_directories(third_party/cryptopp/) +include_directories(third_party/cityhash/include) include_directories(third_party/result/include/) add_compile_definitions(NOMINMAX) diff --git a/include/PICA/dynapica/shader_rec.hpp b/include/PICA/dynapica/shader_rec.hpp index 48104364..2dbb8300 100644 --- a/include/PICA/dynapica/shader_rec.hpp +++ b/include/PICA/dynapica/shader_rec.hpp @@ -1,22 +1,35 @@ #pragma once #include "PICA/shader.hpp" +#if defined(PANDA3DS_DYNAPICA_SUPPORTED) && defined(PANDA3DS_X64_HOST) +#define PANDA3DS_SHADER_JIT_SUPPORTED +#include +#endif + class ShaderJIT { +#ifdef PANDA3DS_SHADER_JIT_SUPPORTED + using Hash = PICAShader::Hash; + using ShaderCache = std::unordered_map; + + ShaderCache cache; void compileShader(PICAShader& shaderUnit); +#endif public: -#if defined(PANDA3DS_DYNAPICA_SUPPORTED) && defined(PANDA3DS_X64_HOST) - #define PANDA3DS_SHADER_JIT_SUPPORTED - +#ifdef PANDA3DS_SHADER_JIT_SUPPORTED // Call this before starting to process a batch of vertices // This will read the PICA config (uploaded shader and shader operand descriptors) and search if we've already compiled this shader // If yes, it sets it as the active shader. if not, then it compiles it, adds it to the cache, and sets it as active, void prepare(PICAShader& shaderUnit); + void reset(); + static constexpr bool isAvailable() { return true; } #else void prepare(PICAShader& shaderUnit) { Helpers::panic("Vertex Loader JIT: Tried to load vertices with JIT on platform that does not support vertex loader jit"); } + + void reset() {} static constexpr bool isAvailable() { return false; } #endif diff --git a/include/PICA/gpu.hpp b/include/PICA/gpu.hpp index 347dd946..4d3d4d99 100644 --- a/include/PICA/gpu.hpp +++ b/include/PICA/gpu.hpp @@ -16,6 +16,8 @@ class GPU { Memory& mem; ShaderUnit shaderUnit; + ShaderJIT shaderJIT; // Doesn't do anything if JIT is disabled or not supported + u8* vram = nullptr; MAKE_LOG_FUNCTION(log, gpuLogger) diff --git a/include/PICA/shader.hpp b/include/PICA/shader.hpp index 65b8da10..6f3d62f8 100644 --- a/include/PICA/shader.hpp +++ b/include/PICA/shader.hpp @@ -89,6 +89,17 @@ protected: std::array callInfo; ShaderType type; + // We use a hashmap for matching 3DS shaders to their equivalent compiled code in our shader cache in the shader JIT + // We choose our hash type to be a 64-bit integer by default, as the collision chance is very tiny and generating it is decently optimal + // Ideally we want to be able to support multiple different types of hash depending on compilation settings, but let's get this working first + using Hash = u64; + + Hash lastCodeHash = 0; // Last hash computed for the shader code (Used for the JIT caching mechanism) + Hash lastOpdescHash = 0; // Last hash computed for the operand descriptors (Also used for the JIT) + + bool codeHashDirty = false; + bool opdescHashDirty = false; + friend class ShaderJIT; private: @@ -204,11 +215,15 @@ public: if (bufferIndex >= 4095) Helpers::panic("o no, shader upload overflew"); bufferedShader[bufferIndex++] = word; bufferIndex &= 0xfff; + + codeHashDirty = true; // Signal the JIT if necessary that the program hash has potentially changed } void uploadDescriptor(u32 word) { operandDescriptors[opDescriptorIndex++] = word; opDescriptorIndex &= 0x7f; + + opdescHashDirty = true; // Signal the JIT if necessary that the program hash has potentially changed } void setFloatUniformIndex(u32 word) { @@ -250,4 +265,7 @@ public: void run(); void reset(); + + Hash getCodeHash(); + Hash getOpdescHash(); }; \ No newline at end of file diff --git a/src/core/PICA/dynapica/shader_rec.cpp b/src/core/PICA/dynapica/shader_rec.cpp index 8f04d1f2..63ea504b 100644 --- a/src/core/PICA/dynapica/shader_rec.cpp +++ b/src/core/PICA/dynapica/shader_rec.cpp @@ -1,7 +1,14 @@ #include "PICA/dynapica/shader_rec.hpp" +#include "cityhash.hpp" #ifdef PANDA3DS_SHADER_JIT_SUPPORTED +void ShaderJIT::reset() { + cache.clear(); +} + void ShaderJIT::prepare(PICAShader& shaderUnit) { - printf("HAPPY HAPPY HAPPY\n"); + // We construct a shader hash from both the code and operand descriptor hashes + // This is so that if only one of them changes, we still properly recompile the shader + Hash hash = shaderUnit.getCodeHash() ^ shaderUnit.getOpdescHash(); } #endif // PANDA3DS_SHADER_JIT_SUPPORTED \ No newline at end of file diff --git a/src/core/PICA/gpu.cpp b/src/core/PICA/gpu.cpp index e11b07a2..d7fa8ce8 100644 --- a/src/core/PICA/gpu.cpp +++ b/src/core/PICA/gpu.cpp @@ -12,6 +12,7 @@ GPU::GPU(Memory& mem) : mem(mem), renderer(*this, regs) { void GPU::reset() { regs.fill(0); shaderUnit.reset(); + shaderJIT.reset(); std::memset(vram, 0, vramSize); totalAttribCount = 0; @@ -84,6 +85,10 @@ void GPU::drawArrays() { log("PICA::DrawElements(vertex count = %d, index buffer config = %08X)\n", vertexCount, indexBufferConfig); } + if constexpr (useShaderJIT) { + shaderJIT.prepare(shaderUnit.vs); + } + // Total number of input attributes to shader. Differs between GS and VS. Currently stubbed to the VS one, as we don't have geometry shaders. const u32 inputAttrCount = (regs[PICAInternalRegs::VertexShaderInputBufferCfg] & 0xf) + 1; const u64 inputAttrCfg = getVertexShaderInputConfig(); diff --git a/src/core/PICA/shader_unit.cpp b/src/core/PICA/shader_unit.cpp index 6e9ca9b6..29541bd0 100644 --- a/src/core/PICA/shader_unit.cpp +++ b/src/core/PICA/shader_unit.cpp @@ -1,4 +1,5 @@ #include "PICA/shader_unit.hpp" +#include "cityhash.hpp" void ShaderUnit::reset() { vs.reset(); @@ -30,4 +31,29 @@ void PICAShader::reset() { addrRegister.x() = 0; addrRegister.y() = 0; loopCounter = 0; + + codeHashDirty = true; + opdescHashDirty = true; +} + +PICAShader::Hash PICAShader::getCodeHash() { + // Hash the code again if the code changed + if (codeHashDirty) { + codeHashDirty = false; + lastCodeHash = CityHash::CityHash64((const char*)&loadedShader[0], loadedShader.size() * sizeof(loadedShader[0])); + } + + // Return the code hash + return lastCodeHash; +} + +PICAShader::Hash PICAShader::getOpdescHash() { + // Hash the code again if the operand descriptors changed + if (opdescHashDirty) { + opdescHashDirty = false; + lastOpdescHash = CityHash::CityHash64((const char*)&operandDescriptors[0], operandDescriptors.size() * sizeof(operandDescriptors[0])); + } + + // Return the code hash + return lastOpdescHash; } \ No newline at end of file diff --git a/third_party/cityhash/cityhash.cpp b/third_party/cityhash/cityhash.cpp index a9d5406b..366d7524 100644 --- a/third_party/cityhash/cityhash.cpp +++ b/third_party/cityhash/cityhash.cpp @@ -29,7 +29,7 @@ #include #include // for memcpy and memset -#include "cityhash.hpp" +#include "include/cityhash.hpp" #include "swap.hpp" // #include "config.h" diff --git a/third_party/cityhash/cityhash.hpp b/third_party/cityhash/include/cityhash.hpp similarity index 100% rename from third_party/cityhash/cityhash.hpp rename to third_party/cityhash/include/cityhash.hpp From 6a70edca7e249094eb7e35eb373e719cc514e697 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Wed, 7 Jun 2023 21:25:46 +0300 Subject: [PATCH 008/103] [Shader JIT[ Cache harder --- CMakeLists.txt | 1 + include/PICA/dynapica/shader_rec.hpp | 8 +++++++- src/core/PICA/dynapica/shader_rec.cpp | 11 +++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 95ea1066..f69b5d85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,7 @@ endif() # Check for arm64 if (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64") set(HOST_ARM64 TRUE) + add_compile_definitions(PANDA3DS_ARM64_HOST) else() set(HOST_ARM64 FALSE) endif() diff --git a/include/PICA/dynapica/shader_rec.hpp b/include/PICA/dynapica/shader_rec.hpp index 2dbb8300..09ff5664 100644 --- a/include/PICA/dynapica/shader_rec.hpp +++ b/include/PICA/dynapica/shader_rec.hpp @@ -3,13 +3,19 @@ #if defined(PANDA3DS_DYNAPICA_SUPPORTED) && defined(PANDA3DS_X64_HOST) #define PANDA3DS_SHADER_JIT_SUPPORTED +#include #include + +#ifdef PANDA3DS_X64_HOST +#include "xbyak/xbyak.h" +using ShaderEmitter = Xbyak::CodeGenerator; +#endif #endif class ShaderJIT { #ifdef PANDA3DS_SHADER_JIT_SUPPORTED using Hash = PICAShader::Hash; - using ShaderCache = std::unordered_map; + using ShaderCache = std::unordered_map>; ShaderCache cache; void compileShader(PICAShader& shaderUnit); diff --git a/src/core/PICA/dynapica/shader_rec.cpp b/src/core/PICA/dynapica/shader_rec.cpp index 63ea504b..e5a08caa 100644 --- a/src/core/PICA/dynapica/shader_rec.cpp +++ b/src/core/PICA/dynapica/shader_rec.cpp @@ -9,6 +9,17 @@ void ShaderJIT::reset() { void ShaderJIT::prepare(PICAShader& shaderUnit) { // We construct a shader hash from both the code and operand descriptor hashes // This is so that if only one of them changes, we still properly recompile the shader + // This code is inspired from how Citra solves this problem Hash hash = shaderUnit.getCodeHash() ^ shaderUnit.getOpdescHash(); + auto it = cache.find(hash); + + if (it == cache.end()) { // Block has not been compiled yet + auto emitter = std::make_unique(); + cache.emplace_hint(it, hash, std::move(emitter)); + } else { // Block has been compiled and found, use it + + } + + shaderUnit.pc = shaderUnit.entrypoint; } #endif // PANDA3DS_SHADER_JIT_SUPPORTED \ No newline at end of file From 415e276ef9789b02532232f69b2369525653fb36 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Thu, 8 Jun 2023 18:51:03 +0300 Subject: [PATCH 009/103] [Shader JIT] Moar --- CMakeLists.txt | 3 ++- include/PICA/dynapica/shader_rec.hpp | 9 ++++++-- .../PICA/dynapica/shader_rec_emitter_x64.hpp | 23 +++++++++++++++++++ include/PICA/shader.hpp | 7 ++++-- src/core/PICA/dynapica/shader_rec.cpp | 6 ++--- .../PICA/dynapica/shader_rec_emitter_x64.cpp | 11 +++++++++ .../dynapica/shader_rec_instructions_x64.cpp | 0 7 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 include/PICA/dynapica/shader_rec_emitter_x64.hpp create mode 100644 src/core/PICA/dynapica/shader_rec_emitter_x64.cpp delete mode 100644 src/core/PICA/dynapica/shader_rec_instructions_x64.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f69b5d85..ed3d298c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,7 +94,7 @@ set(SERVICE_SOURCE_FILES src/core/services/service_manager.cpp src/core/services ) set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp src/core/PICA/regs.cpp src/core/PICA/shader_unit.cpp src/core/PICA/shader_interpreter.cpp src/core/PICA/dynapica/shader_rec.cpp - src/core/PICA/dynapica/shader_rec_instructions_x64.cpp + src/core/PICA/dynapica/shader_rec_emitter_x64.cpp ) set(RENDERER_GL_SOURCE_FILES src/core/renderer_gl/renderer_gl.cpp src/core/renderer_gl/textures.cpp src/core/renderer_gl/etc1.cpp) @@ -124,6 +124,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp inc include/services/ldr_ro.hpp include/ipc.hpp include/services/act.hpp include/services/nfc.hpp include/system_models.hpp include/services/dlp_srvr.hpp include/PICA/dynapica/pica_recs.hpp include/PICA/dynapica/x64_regs.hpp include/PICA/dynapica/vertex_loader_rec.hpp include/PICA/dynapica/shader_rec.hpp + include/PICA/dynapica/shader_rec_emitter_x64.hpp ) set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp diff --git a/include/PICA/dynapica/shader_rec.hpp b/include/PICA/dynapica/shader_rec.hpp index 09ff5664..9aa97947 100644 --- a/include/PICA/dynapica/shader_rec.hpp +++ b/include/PICA/dynapica/shader_rec.hpp @@ -7,8 +7,7 @@ #include #ifdef PANDA3DS_X64_HOST -#include "xbyak/xbyak.h" -using ShaderEmitter = Xbyak::CodeGenerator; +#include "shader_rec_emitter_x64.hpp" #endif #endif @@ -16,6 +15,7 @@ class ShaderJIT { #ifdef PANDA3DS_SHADER_JIT_SUPPORTED using Hash = PICAShader::Hash; using ShaderCache = std::unordered_map>; + ShaderEmitter::Callback activeShaderCallback; ShaderCache cache; void compileShader(PICAShader& shaderUnit); @@ -35,8 +35,13 @@ public: Helpers::panic("Vertex Loader JIT: Tried to load vertices with JIT on platform that does not support vertex loader jit"); } + // Define dummy callback. This should never be called if the shader JIT is not supported + using Callback = void(*)(PICAShader& shaderUnit); + Callback activeShaderCallback = nullptr; + void reset() {} static constexpr bool isAvailable() { return false; } #endif + auto getCallback() { return activeShaderCallback; } }; \ No newline at end of file diff --git a/include/PICA/dynapica/shader_rec_emitter_x64.hpp b/include/PICA/dynapica/shader_rec_emitter_x64.hpp new file mode 100644 index 00000000..b4022587 --- /dev/null +++ b/include/PICA/dynapica/shader_rec_emitter_x64.hpp @@ -0,0 +1,23 @@ +#pragma once + +// Only do anything if we're on an x64 target with JIT support enabled +#if defined(PANDA3DS_DYNAPICA_SUPPORTED) && defined(PANDA3DS_X64_HOST) +#include "helpers.hpp" +#include "PICA/shader.hpp" +#include "xbyak/xbyak.h" +#include "x64_regs.hpp" + +class ShaderEmitter : public Xbyak::CodeGenerator { + static constexpr size_t executableMemorySize = PICAShader::maxInstructionCount * 96; // How much executable memory to alloc for each shader + // Allocate some extra space as padding for security purposes in the extremely unlikely occasion we manage to overflow the above size + static constexpr size_t allocSize = executableMemorySize + 0x1000; + +public: + using Callback = void(*)(const PICAShader& shaderUnit); + + // Initialize our emitter with "allocSize" bytes of RWX memory + ShaderEmitter() : Xbyak::CodeGenerator(allocSize) {} + void compile(const PICAShader& shaderUnit); +}; + +#endif // x64 recompiler check \ No newline at end of file diff --git a/include/PICA/shader.hpp b/include/PICA/shader.hpp index 6f3d62f8..a2f830b1 100644 --- a/include/PICA/shader.hpp +++ b/include/PICA/shader.hpp @@ -100,7 +100,9 @@ protected: bool codeHashDirty = false; bool opdescHashDirty = false; + // Add these as friend classes for the JIT so it has access to all important state friend class ShaderJIT; + friend class ShaderEmitter; private: vec4f getSource(u32 source); @@ -184,8 +186,9 @@ private: bool isCondTrue(u32 instruction); public: - std::array loadedShader; // Currently loaded & active shader - std::array bufferedShader; // Shader to be transferred when the SH_CODETRANSFER_END reg gets written to + static constexpr size_t maxInstructionCount = 4096; + std::array loadedShader; // Currently loaded & active shader + std::array bufferedShader; // Shader to be transferred when the SH_CODETRANSFER_END reg gets written to u32 entrypoint = 0; // Initial shader PC u32 boolUniform; diff --git a/src/core/PICA/dynapica/shader_rec.cpp b/src/core/PICA/dynapica/shader_rec.cpp index e5a08caa..3029c0e2 100644 --- a/src/core/PICA/dynapica/shader_rec.cpp +++ b/src/core/PICA/dynapica/shader_rec.cpp @@ -7,6 +7,7 @@ void ShaderJIT::reset() { } void ShaderJIT::prepare(PICAShader& shaderUnit) { + shaderUnit.pc = shaderUnit.entrypoint; // We construct a shader hash from both the code and operand descriptor hashes // This is so that if only one of them changes, we still properly recompile the shader // This code is inspired from how Citra solves this problem @@ -15,11 +16,10 @@ void ShaderJIT::prepare(PICAShader& shaderUnit) { if (it == cache.end()) { // Block has not been compiled yet auto emitter = std::make_unique(); + emitter->compile(shaderUnit); cache.emplace_hint(it, hash, std::move(emitter)); } else { // Block has been compiled and found, use it - + auto emitter = it->second.get(); } - - shaderUnit.pc = shaderUnit.entrypoint; } #endif // PANDA3DS_SHADER_JIT_SUPPORTED \ No newline at end of file diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp new file mode 100644 index 00000000..0511cd95 --- /dev/null +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -0,0 +1,11 @@ +#if defined(PANDA3DS_DYNAPICA_SUPPORTED) && defined(PANDA3DS_X64_HOST) +#include "PICA/dynapica/shader_rec_emitter_x64.hpp" + +using namespace Xbyak; +using namespace Xbyak::util; + +void ShaderEmitter::compile(const PICAShader& shaderUnit) { + +} + +#endif \ No newline at end of file diff --git a/src/core/PICA/dynapica/shader_rec_instructions_x64.cpp b/src/core/PICA/dynapica/shader_rec_instructions_x64.cpp deleted file mode 100644 index e69de29b..00000000 From 77cba3110d4d1bfac407da84645d2aa9b9eb858d Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Thu, 8 Jun 2023 22:22:28 +0300 Subject: [PATCH 010/103] [Shader JIT] Add prologue & some more compilation stuffs --- include/PICA/dynapica/shader_rec.hpp | 10 +-- .../PICA/dynapica/shader_rec_emitter_x64.hpp | 36 ++++++++++- src/core/PICA/dynapica/shader_rec.cpp | 6 ++ .../PICA/dynapica/shader_rec_emitter_x64.cpp | 64 ++++++++++++++++++- src/core/PICA/gpu.cpp | 7 +- 5 files changed, 116 insertions(+), 7 deletions(-) diff --git a/include/PICA/dynapica/shader_rec.hpp b/include/PICA/dynapica/shader_rec.hpp index 9aa97947..711dabb0 100644 --- a/include/PICA/dynapica/shader_rec.hpp +++ b/include/PICA/dynapica/shader_rec.hpp @@ -15,10 +15,10 @@ class ShaderJIT { #ifdef PANDA3DS_SHADER_JIT_SUPPORTED using Hash = PICAShader::Hash; using ShaderCache = std::unordered_map>; - ShaderEmitter::Callback activeShaderCallback; + ShaderEmitter::PrologueCallback prologueCallback; + ShaderEmitter::InstructionCallback entrypointCallback; ShaderCache cache; - void compileShader(PICAShader& shaderUnit); #endif public: @@ -26,8 +26,12 @@ public: // Call this before starting to process a batch of vertices // This will read the PICA config (uploaded shader and shader operand descriptors) and search if we've already compiled this shader // If yes, it sets it as the active shader. if not, then it compiles it, adds it to the cache, and sets it as active, + // The caller must make sure the entrypoint has been properly set beforehand void prepare(PICAShader& shaderUnit); void reset(); + void run(PICAShader& shaderUnit) { + prologueCallback(shaderUnit, entrypointCallback); + } static constexpr bool isAvailable() { return true; } #else @@ -42,6 +46,4 @@ public: void reset() {} static constexpr bool isAvailable() { return false; } #endif - - auto getCallback() { return activeShaderCallback; } }; \ No newline at end of file diff --git a/include/PICA/dynapica/shader_rec_emitter_x64.hpp b/include/PICA/dynapica/shader_rec_emitter_x64.hpp index b4022587..5b73e63b 100644 --- a/include/PICA/dynapica/shader_rec_emitter_x64.hpp +++ b/include/PICA/dynapica/shader_rec_emitter_x64.hpp @@ -7,17 +7,51 @@ #include "xbyak/xbyak.h" #include "x64_regs.hpp" +#include + class ShaderEmitter : public Xbyak::CodeGenerator { static constexpr size_t executableMemorySize = PICAShader::maxInstructionCount * 96; // How much executable memory to alloc for each shader // Allocate some extra space as padding for security purposes in the extremely unlikely occasion we manage to overflow the above size static constexpr size_t allocSize = executableMemorySize + 0x1000; + // An array of labels (incl pointers) to each compiled (to x64) PICA instruction + std::array instructionLabels; + // A vector of PCs that can potentially return based on the state of the PICA callstack. + // Filled before compiling a shader by scanning the code for call instructions + std::vector returnPCs; + + u32 recompilerPC; // PC the recompiler is currently recompiling @ + + // Compile all instructions from [current recompiler PC, end) + void compileUntil(const PICAShader& shaderUnit, u32 endPC); + // Compile instruction "instr" + void compileInstruction(const PICAShader& shaderUnit); + + bool isCall(u32 instruction) { + const u32 opcode = instruction >> 26; + return (opcode == ShaderOpcodes::CALL) || (opcode == ShaderOpcodes::CALLC) || (opcode == ShaderOpcodes::CALLU); + } + // Scan the shader code for call instructions to fill up the returnPCs vector before starting compilation + void scanForCalls(const PICAShader& shader); + public: - using Callback = void(*)(const PICAShader& shaderUnit); + using InstructionCallback = void(*)(PICAShader& shaderUnit); // Callback type used for instructions + // Callback type used for the JIT prologue. This is what the caller will call + using PrologueCallback = void(*)(PICAShader& shaderUnit, InstructionCallback cb); + PrologueCallback prologueCb; // Initialize our emitter with "allocSize" bytes of RWX memory ShaderEmitter() : Xbyak::CodeGenerator(allocSize) {} void compile(const PICAShader& shaderUnit); + + // PC must be a valid entrypoint here. It doesn't have that much overhead in this case, so we use std::array<>::at() to assert it does + InstructionCallback getInstructionCallback(u32 pc) { + return reinterpret_cast(instructionLabels.at(pc).getAddress()); + } + + PrologueCallback getPrologueCallback() { + return prologueCb; + } }; #endif // x64 recompiler check \ No newline at end of file diff --git a/src/core/PICA/dynapica/shader_rec.cpp b/src/core/PICA/dynapica/shader_rec.cpp index 3029c0e2..0e182b60 100644 --- a/src/core/PICA/dynapica/shader_rec.cpp +++ b/src/core/PICA/dynapica/shader_rec.cpp @@ -17,9 +17,15 @@ void ShaderJIT::prepare(PICAShader& shaderUnit) { if (it == cache.end()) { // Block has not been compiled yet auto emitter = std::make_unique(); emitter->compile(shaderUnit); + // Get pointer to callbacks + entrypointCallback = emitter->getInstructionCallback(shaderUnit.entrypoint); + prologueCallback = emitter->getPrologueCallback(); + cache.emplace_hint(it, hash, std::move(emitter)); } else { // Block has been compiled and found, use it auto emitter = it->second.get(); + entrypointCallback = emitter->getInstructionCallback(shaderUnit.entrypoint); + prologueCallback = emitter->getPrologueCallback(); } } #endif // PANDA3DS_SHADER_JIT_SUPPORTED \ No newline at end of file diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 0511cd95..6179df0b 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -4,8 +4,70 @@ using namespace Xbyak; using namespace Xbyak::util; -void ShaderEmitter::compile(const PICAShader& shaderUnit) { +// Register that points to PICA state +static constexpr Reg64 statePointer = rbp; +void ShaderEmitter::compile(const PICAShader& shaderUnit) { + // Emit prologue first + align(16); + prologueCb = getCurr(); + + // We assume arg1 contains the pointer to the PICA state and arg2 a pointer to the code for the entrypoint + push(statePointer); // Back up state pointer to stack. This also aligns rsp to 16 bytes for calls + mov(statePointer, (uintptr_t)&shaderUnit); // Set state pointer to the proper pointer + + // If we add integer register allocations they should be pushed here, and the rsp should be properly fixed up + // However most of the PICA is floating point so yeah + + // Allocate shadow stack on Windows + if constexpr (isWindows()) { + sub(rsp, 32); + } + // Tail call to shader code entrypoint + jmp(arg2); + align(16); + // Scan the shader code for call instructions and add them to the list of possible return PCs. We need to do this because the PICA callstack works + // Pretty weirdly + scanForCalls(shaderUnit); + + // Compile every instruction in the shader + // This sounds horrible but the PICA instruction memory is tiny, and most of the time it's padded wtih nops that compile to nothing + recompilerPC = 0; + compileUntil(shaderUnit, PICAShader::maxInstructionCount); +} + +void ShaderEmitter::scanForCalls(const PICAShader& shaderUnit) { + returnPCs.clear(); + + for (u32 i = 0; i < PICAShader::maxInstructionCount; i++) { + const u32 instruction = shaderUnit.loadedShader[i]; + if (isCall(instruction)) { + const u32 num = instruction & 0xff; // Num of instructions to execute + const u32 dest = (instruction >> 10) & 0xfff; // Starting subroutine address + const u32 returnPC = num + dest; // Add them to get the return PC + + returnPCs.push_back(returnPC); + } + } +} + +void ShaderEmitter::compileUntil(const PICAShader& shaderUnit, u32 end) { + while (recompilerPC < end) { + compileInstruction(shaderUnit); + } +} + +void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { + // Write current location to label for this instruction + L(instructionLabels[recompilerPC]); + // Fetch instruction and inc PC + const u32 instruction = shaderUnit.loadedShader[recompilerPC++]; + const u32 opcode = instruction >> 26; + + switch (opcode) { + default: + Helpers::panic("ShaderJIT: Unimplemented PICA opcode %X", opcode); + } } #endif \ No newline at end of file diff --git a/src/core/PICA/gpu.cpp b/src/core/PICA/gpu.cpp index d7fa8ce8..e0951080 100644 --- a/src/core/PICA/gpu.cpp +++ b/src/core/PICA/gpu.cpp @@ -203,7 +203,12 @@ void GPU::drawArrays() { std::memcpy(&shaderUnit.vs.inputs[mapping], ¤tAttributes[j], sizeof(vec4f)); } - shaderUnit.vs.run(); + if constexpr (useShaderJIT) { + shaderJIT.run(shaderUnit.vs); + } else { + shaderUnit.vs.run(); + } + std::memcpy(&vertices[i].position, &shaderUnit.vs.outputs[0], sizeof(vec4f)); std::memcpy(&vertices[i].colour, &shaderUnit.vs.outputs[1], sizeof(vec4f)); std::memcpy(&vertices[i].UVs, &shaderUnit.vs.outputs[2], 2 * sizeof(f24)); From d5eea4b58c5739d594a3703e560c14fdc5f02996 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Thu, 8 Jun 2023 22:44:57 +0300 Subject: [PATCH 011/103] [ShaderJIT] Add const qualifier to JIT callbacks --- .../PICA/dynapica/shader_rec_emitter_x64.hpp | 4 ++-- .../PICA/dynapica/shader_rec_emitter_x64.cpp | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/include/PICA/dynapica/shader_rec_emitter_x64.hpp b/include/PICA/dynapica/shader_rec_emitter_x64.hpp index 5b73e63b..16b7bc89 100644 --- a/include/PICA/dynapica/shader_rec_emitter_x64.hpp +++ b/include/PICA/dynapica/shader_rec_emitter_x64.hpp @@ -35,9 +35,9 @@ class ShaderEmitter : public Xbyak::CodeGenerator { void scanForCalls(const PICAShader& shader); public: - using InstructionCallback = void(*)(PICAShader& shaderUnit); // Callback type used for instructions + using InstructionCallback = const void(*)(PICAShader& shaderUnit); // Callback type used for instructions // Callback type used for the JIT prologue. This is what the caller will call - using PrologueCallback = void(*)(PICAShader& shaderUnit, InstructionCallback cb); + using PrologueCallback = const void(*)(PICAShader& shaderUnit, InstructionCallback cb); PrologueCallback prologueCb; // Initialize our emitter with "allocSize" bytes of RWX memory diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 6179df0b..0182a032 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -1,11 +1,17 @@ #if defined(PANDA3DS_DYNAPICA_SUPPORTED) && defined(PANDA3DS_X64_HOST) #include "PICA/dynapica/shader_rec_emitter_x64.hpp" +#include +#include + using namespace Xbyak; using namespace Xbyak::util; // Register that points to PICA state static constexpr Reg64 statePointer = rbp; +static constexpr Xmm scratch1 = xmm0; +static constexpr Xmm scratch2 = xmm1; +static constexpr Xmm scratch3 = xmm2; void ShaderEmitter::compile(const PICAShader& shaderUnit) { // Emit prologue first @@ -65,9 +71,27 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { const u32 opcode = instruction >> 26; switch (opcode) { + case ShaderOpcodes::MOV: recMOV(shaderUnit, instruction); break; default: Helpers::panic("ShaderJIT: Unimplemented PICA opcode %X", opcode); } } +void ShaderEmitter::loadRegister(Xmm dest, const PICAShader& shader, u32 srcReg, u32 index) { + +} + +void ShaderEmitter::recMOV(const PICAShader& shader, u32 instruction) { + const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; + u32 src = (instruction >> 12) & 0x7f; + const u32 idx = (instruction >> 19) & 3; + const u32 dest = (instruction >> 21) & 0x1f; + + src = getIndexedSource(src, idx); + vec4f srcVector = getSourceSwizzled<1>(src, operandDescriptor); + vec4f& destVector = getDest(dest); + + u32 componentMask = operandDescriptor & 0xf; +} + #endif \ No newline at end of file From 46a47912d8ea364b708a29a6679e62217a74ed25 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Thu, 8 Jun 2023 22:44:57 +0300 Subject: [PATCH 012/103] [ShaderJIT] Add const qualifier to JIT callbacks --- .../PICA/dynapica/shader_rec_emitter_x64.hpp | 4 +-- .../PICA/dynapica/shader_rec_emitter_x64.cpp | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/include/PICA/dynapica/shader_rec_emitter_x64.hpp b/include/PICA/dynapica/shader_rec_emitter_x64.hpp index 5b73e63b..16b7bc89 100644 --- a/include/PICA/dynapica/shader_rec_emitter_x64.hpp +++ b/include/PICA/dynapica/shader_rec_emitter_x64.hpp @@ -35,9 +35,9 @@ class ShaderEmitter : public Xbyak::CodeGenerator { void scanForCalls(const PICAShader& shader); public: - using InstructionCallback = void(*)(PICAShader& shaderUnit); // Callback type used for instructions + using InstructionCallback = const void(*)(PICAShader& shaderUnit); // Callback type used for instructions // Callback type used for the JIT prologue. This is what the caller will call - using PrologueCallback = void(*)(PICAShader& shaderUnit, InstructionCallback cb); + using PrologueCallback = const void(*)(PICAShader& shaderUnit, InstructionCallback cb); PrologueCallback prologueCb; // Initialize our emitter with "allocSize" bytes of RWX memory diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 6179df0b..57950d17 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -1,11 +1,17 @@ #if defined(PANDA3DS_DYNAPICA_SUPPORTED) && defined(PANDA3DS_X64_HOST) #include "PICA/dynapica/shader_rec_emitter_x64.hpp" +#include +#include + using namespace Xbyak; using namespace Xbyak::util; // Register that points to PICA state static constexpr Reg64 statePointer = rbp; +static constexpr Xmm scratch1 = xmm0; +static constexpr Xmm scratch2 = xmm1; +static constexpr Xmm scratch3 = xmm2; void ShaderEmitter::compile(const PICAShader& shaderUnit) { // Emit prologue first @@ -65,9 +71,29 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { const u32 opcode = instruction >> 26; switch (opcode) { + case ShaderOpcodes::MOV: recMOV(shaderUnit, instruction); break; default: Helpers::panic("ShaderJIT: Unimplemented PICA opcode %X", opcode); } } +void ShaderEmitter::loadRegister(Xmm dest, const PICAShader& shader, u32 srcReg, u32 index) { + +} + +void ShaderEmitter::recMOV(const PICAShader& shader, u32 instruction) { + /* + const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; + u32 src = (instruction >> 12) & 0x7f; + const u32 idx = (instruction >> 19) & 3; + const u32 dest = (instruction >> 21) & 0x1f; + + src = getIndexedSource(src, idx); + vec4f srcVector = getSourceSwizzled<1>(src, operandDescriptor); + vec4f& destVector = getDest(dest); + + u32 componentMask = operandDescriptor & 0xf; + */ +} + #endif \ No newline at end of file From fb11fd44402e6852754d6786a1921c57160def50 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Thu, 8 Jun 2023 22:50:59 +0300 Subject: [PATCH 013/103] Fix merge conflict screwing up everything --- include/PICA/dynapica/shader_rec_emitter_x64.hpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/include/PICA/dynapica/shader_rec_emitter_x64.hpp b/include/PICA/dynapica/shader_rec_emitter_x64.hpp index 16b7bc89..99517bd1 100644 --- a/include/PICA/dynapica/shader_rec_emitter_x64.hpp +++ b/include/PICA/dynapica/shader_rec_emitter_x64.hpp @@ -32,7 +32,13 @@ class ShaderEmitter : public Xbyak::CodeGenerator { return (opcode == ShaderOpcodes::CALL) || (opcode == ShaderOpcodes::CALLC) || (opcode == ShaderOpcodes::CALLU); } // Scan the shader code for call instructions to fill up the returnPCs vector before starting compilation - void scanForCalls(const PICAShader& shader); + void scanForCalls(const PICAShader& shaderUnit); + + // Load register with number "srcReg" indexed by index "idx" into the xmm register "reg" + void loadRegister(Xmm dest, const PICAShader& shader, u32 srcReg, u32 idx); + + // Instruction recompilation functions + void recMOV(const PICAShader& shader, u32 instruction); public: using InstructionCallback = const void(*)(PICAShader& shaderUnit); // Callback type used for instructions From 9bb1f31fc9bc5cee7b8cf820d5a5b370e41b2060 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Fri, 9 Jun 2023 00:46:17 +0300 Subject: [PATCH 014/103] [ShaderJIT] Add MOV --- .../PICA/dynapica/shader_rec_emitter_x64.hpp | 30 ++++- include/PICA/shader.hpp | 13 ++- .../PICA/dynapica/shader_rec_emitter_x64.cpp | 108 ++++++++++++++++-- 3 files changed, 131 insertions(+), 20 deletions(-) diff --git a/include/PICA/dynapica/shader_rec_emitter_x64.hpp b/include/PICA/dynapica/shader_rec_emitter_x64.hpp index 99517bd1..5c3c403c 100644 --- a/include/PICA/dynapica/shader_rec_emitter_x64.hpp +++ b/include/PICA/dynapica/shader_rec_emitter_x64.hpp @@ -5,6 +5,7 @@ #include "helpers.hpp" #include "PICA/shader.hpp" #include "xbyak/xbyak.h" +#include "xbyak/xbyak_util.h" #include "x64_regs.hpp" #include @@ -14,13 +15,20 @@ class ShaderEmitter : public Xbyak::CodeGenerator { // Allocate some extra space as padding for security purposes in the extremely unlikely occasion we manage to overflow the above size static constexpr size_t allocSize = executableMemorySize + 0x1000; + // If the swizzle field is this value then the swizzle pattern is .xyzw so we don't need a shuffle + static constexpr uint noSwizzle = 0x1B; + + using f24 = Floats::f24; + using vec4f = OpenGL::Vector; + // An array of labels (incl pointers) to each compiled (to x64) PICA instruction std::array instructionLabels; // A vector of PCs that can potentially return based on the state of the PICA callstack. // Filled before compiling a shader by scanning the code for call instructions std::vector returnPCs; - u32 recompilerPC; // PC the recompiler is currently recompiling @ + u32 recompilerPC = 0; // PC the recompiler is currently recompiling @ + bool haveSSE4_1 = false; // Shows if the CPU supports SSE4.1 // Compile all instructions from [current recompiler PC, end) void compileUntil(const PICAShader& shaderUnit, u32 endPC); @@ -35,7 +43,12 @@ class ShaderEmitter : public Xbyak::CodeGenerator { void scanForCalls(const PICAShader& shaderUnit); // Load register with number "srcReg" indexed by index "idx" into the xmm register "reg" - void loadRegister(Xmm dest, const PICAShader& shader, u32 srcReg, u32 idx); + template + void loadRegister(Xmm dest, const PICAShader& shader, u32 src, u32 idx, u32 operandDescriptor); + void storeRegister(Xmm source, const PICAShader& shader, u32 dest, u32 operandDescriptor); + + const vec4f& getSourceRef(const PICAShader& shader, u32 src); + const vec4f& getDestRef(const PICAShader& shader, u32 dest); // Instruction recompilation functions void recMOV(const PICAShader& shader, u32 instruction); @@ -44,15 +57,22 @@ public: using InstructionCallback = const void(*)(PICAShader& shaderUnit); // Callback type used for instructions // Callback type used for the JIT prologue. This is what the caller will call using PrologueCallback = const void(*)(PICAShader& shaderUnit, InstructionCallback cb); - PrologueCallback prologueCb; + PrologueCallback prologueCb = nullptr; // Initialize our emitter with "allocSize" bytes of RWX memory - ShaderEmitter() : Xbyak::CodeGenerator(allocSize) {} + ShaderEmitter() : Xbyak::CodeGenerator(allocSize) { + const auto cpu = Xbyak::util::Cpu(); + + haveSSE4_1 = cpu.has(Xbyak::util::Cpu::tSSE41); + } + void compile(const PICAShader& shaderUnit); // PC must be a valid entrypoint here. It doesn't have that much overhead in this case, so we use std::array<>::at() to assert it does InstructionCallback getInstructionCallback(u32 pc) { - return reinterpret_cast(instructionLabels.at(pc).getAddress()); + // Cast away the constness because casting to a function pointer is hard otherwise. Legal as long as we don't write to *ptr + uint8_t* ptr = const_cast(instructionLabels.at(pc).getAddress()); + return reinterpret_cast(ptr); } PrologueCallback getPrologueCallback() { diff --git a/include/PICA/shader.hpp b/include/PICA/shader.hpp index a2f830b1..284438bf 100644 --- a/include/PICA/shader.hpp +++ b/include/PICA/shader.hpp @@ -42,6 +42,7 @@ namespace ShaderOpcodes { }; } +// Note: All PICA f24 vec4 registers must have the alignas(16) specifier to make them easier to access in SSE/NEON code in the JIT class PICAShader { using f24 = Floats::f24; using vec4f = OpenGL::Vector; @@ -74,7 +75,7 @@ class PICAShader { protected: std::array operandDescriptors; - std::array tempRegisters; // General purpose registers the shader can use for temp values + alignas(16) std::array tempRegisters; // General purpose registers the shader can use for temp values OpenGL::Vector addrRegister; // Address register bool cmpRegister[2]; // Comparison registers where the result of CMP is stored in u32 loopCounter; @@ -104,10 +105,10 @@ protected: friend class ShaderJIT; friend class ShaderEmitter; -private: vec4f getSource(u32 source); vec4f& getDest(u32 dest); +private: // Interpreter functions for the various shader functions void add(u32 instruction); void call(u32 instruction); @@ -193,11 +194,11 @@ public: u32 entrypoint = 0; // Initial shader PC u32 boolUniform; std::array, 4> intUniforms; - std::array floatUniforms; + alignas(16) std::array floatUniforms; - std::array fixedAttributes; // Fixed vertex attributes - std::array inputs; // Attributes passed to the shader - std::array outputs; + alignas(16) std::array fixedAttributes; // Fixed vertex attributes + alignas(16) std::array inputs; // Attributes passed to the shader + alignas(16) std::array outputs; PICAShader(ShaderType type) : type(type) {} diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index bc6bd916..d06b9db2 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -73,27 +73,117 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { switch (opcode) { case ShaderOpcodes::MOV: recMOV(shaderUnit, instruction); break; default: - Helpers::panic("ShaderJIT: Unimplemented PICA opcode %X", opcode); + Helpers::panic("Shader JIT: Unimplemented PICA opcode %X", opcode); } } -void ShaderEmitter::loadRegister(Xmm dest, const PICAShader& shader, u32 srcReg, u32 index) { +const ShaderEmitter::vec4f& ShaderEmitter::getSourceRef(const PICAShader& shader, u32 src) { + alignas(16) static vec4f dummy = vec4f({ f24::zero(), f24::zero(), f24::zero(), f24::zero() }); + if (src < 0x10) + return shader.inputs[src]; + else if (src < 0x20) + return shader.tempRegisters[src - 0x10]; + else if (src <= 0x7f) + return shader.floatUniforms[src - 0x20]; + else { + Helpers::warn("[Shader JIT] Unimplemented source value: %X\n", src); + return dummy; + } +} + +const ShaderEmitter::vec4f& ShaderEmitter::getDestRef(const PICAShader& shader, u32 dest) { + if (dest < 0x10) { + return shader.outputs[dest]; + } else if (dest < 0x20) { + return shader.tempRegisters[dest - 0x10]; + } + Helpers::panic("[Shader JIT] Unimplemented dest: %X", dest); +} + +// See shader.hpp header for docs on how the swizzle and negate works +template +void ShaderEmitter::loadRegister(Xmm dest, const PICAShader& shader, u32 src, u32 index, u32 operandDescriptor) { + u32 compSwizzle; // Component swizzle pattern for the register + bool negate; // If true, negate all lanes of the register + + if constexpr (sourceIndex == 1) { // SRC1 + negate = ((operandDescriptor >> 4) & 1) != 0; + compSwizzle = (operandDescriptor >> 5) & 0xff; + } + else if constexpr (sourceIndex == 2) { // SRC2 + negate = ((operandDescriptor >> 13) & 1) != 0; + compSwizzle = (operandDescriptor >> 14) & 0xff; + } + else if constexpr (sourceIndex == 3) { // SRC3 + negate = ((operandDescriptor >> 22) & 1) != 0; + compSwizzle = (operandDescriptor >> 23) & 0xff; + } + + // PICA has the swizzle descriptor inverted in comparison to x86. For the PICA, the descriptor is (lowest to highest bits) wzyx while it's xyzw for x86 + u32 convertedSwizzle = ((compSwizzle >> 6) & 0b11) | (((compSwizzle >> 4) & 0b11) << 2) | (((compSwizzle >> 2) & 0b11) << 4) | ((compSwizzle & 0b11) << 6); + + switch (index) { + case 0: [[likely]] { // Keep src as is, no need to offset it + const vec4f& srcRef = getSourceRef(shader, src); + const uintptr_t offset = uintptr_t(&srcRef) - uintptr_t(&shader); // Calculate offset of register from start of the state struct + + if (compSwizzle == noSwizzle) // Avoid emitting swizzle if not necessary + movaps(dest, xword[statePointer + offset]); + else // Swizzle is not trivial so we need to emit a shuffle instruction + pshufd(dest, xword[statePointer + offset], convertedSwizzle); + return; + } + + default: + Helpers::panic("[ShaderJIT]: Unimplemented source index type"); + } + + if (negate) { + Helpers::panic("[ShaderJIT] Unimplemented register negation"); + } + + Helpers::panic("Reached unreachable path in PICAShader::getIndexedSource"); +} + +void ShaderEmitter::storeRegister(Xmm source, const PICAShader& shader, u32 dest, u32 operandDescriptor) { + const vec4f& destRef = getDestRef(shader, dest); + const uintptr_t offset = uintptr_t(&destRef) - uintptr_t(&shader); // Calculate offset of register from start of the state struct + + // Mask of which lanes to write + u32 writeMask = operandDescriptor & 0xf; + if (writeMask == 0xf) { // No lanes are masked, just movaps + movaps(xword[statePointer + offset], source); + } else if (haveSSE4_1) { + // Bit reverse the write mask because that is what blendps expects + u32 adjustedMask = ((writeMask >> 3) & 0b1) | ((writeMask >> 1) & 0b10) | ((writeMask << 1) & 0b100) | ((writeMask << 3) & 0b1000); + movaps(scratch1, xword[statePointer + offset]); // Read current value of dest + blendps(scratch1, source, adjustedMask); // Blend with source + movaps(xword[statePointer + offset], scratch1); // Write back + } else { + // Blend algo referenced from Citra + const u8 selector = (((writeMask & 0b1000) ? 1 : 0) << 0) | + (((writeMask & 0b0100) ? 3 : 2) << 2) | + (((writeMask & 0b0010) ? 0 : 1) << 4) | + (((writeMask & 0b0001) ? 2 : 3) << 6); + + movaps(scratch1, xword[statePointer + offset]); + movaps(scratch2, source); + unpckhps(scratch2, scratch1); // Unpack X/Y components of source and destination + unpcklps(scratch1, source); // Unpack Z/W components of source and destination + shufps(scratch1, scratch2, selector); // "merge-shuffle" dest and source using selecto + movaps(xword[statePointer + offset], scratch1); // Write back + } } void ShaderEmitter::recMOV(const PICAShader& shader, u32 instruction) { - /* const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; u32 src = (instruction >> 12) & 0x7f; const u32 idx = (instruction >> 19) & 3; const u32 dest = (instruction >> 21) & 0x1f; - src = getIndexedSource(src, idx); - vec4f srcVector = getSourceSwizzled<1>(src, operandDescriptor); - vec4f& destVector = getDest(dest); - - u32 componentMask = operandDescriptor & 0xf; - */ + loadRegister<1>(scratch1, shader, src, idx, operandDescriptor); // Load source 1 into scratch1 + storeRegister(scratch1, shader, dest, operandDescriptor); } #endif \ No newline at end of file From fd411245fa6638c19ff08f0640e52c528566e86f Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Fri, 9 Jun 2023 02:28:59 +0300 Subject: [PATCH 015/103] [Shader JIT] Get first JIT trangle --- .../PICA/dynapica/shader_rec_emitter_x64.hpp | 6 ++ include/PICA/shader.hpp | 21 +++--- .../PICA/dynapica/shader_rec_emitter_x64.cpp | 73 ++++++++++++++++--- 3 files changed, 82 insertions(+), 18 deletions(-) diff --git a/include/PICA/dynapica/shader_rec_emitter_x64.hpp b/include/PICA/dynapica/shader_rec_emitter_x64.hpp index 5c3c403c..27d1865f 100644 --- a/include/PICA/dynapica/shader_rec_emitter_x64.hpp +++ b/include/PICA/dynapica/shader_rec_emitter_x64.hpp @@ -51,6 +51,9 @@ class ShaderEmitter : public Xbyak::CodeGenerator { const vec4f& getDestRef(const PICAShader& shader, u32 dest); // Instruction recompilation functions + void recADD(const PICAShader& shader, u32 instruction); + void recDP4(const PICAShader& shader, u32 instruction); + void recEND(const PICAShader& shader, u32 instruction); void recMOV(const PICAShader& shader, u32 instruction); public: @@ -64,6 +67,9 @@ public: const auto cpu = Xbyak::util::Cpu(); haveSSE4_1 = cpu.has(Xbyak::util::Cpu::tSSE41); + if (!cpu.has(Xbyak::util::Cpu::tSSE3)) { + Helpers::panic("This CPU does not support SSE3. Please use the shader interpreter instead"); + } } void compile(const PICAShader& shaderUnit); diff --git a/include/PICA/shader.hpp b/include/PICA/shader.hpp index 284438bf..bf550f41 100644 --- a/include/PICA/shader.hpp +++ b/include/PICA/shader.hpp @@ -73,6 +73,18 @@ class PICAShader { std::array floatUniformBuffer; // Buffer for temporarily caching float uniform data +public: + // These are placed close to the temp registers and co because it helps the JIT generate better code + u32 entrypoint = 0; // Initial shader PC + u32 boolUniform; + std::array, 4> intUniforms; + alignas(16) std::array floatUniforms; + + alignas(16) std::array fixedAttributes; // Fixed vertex attributes + alignas(16) std::array inputs; // Attributes passed to the shader + alignas(16) std::array outputs; + alignas(16) vec4f dummy = vec4f({ f24::zero(), f24::zero(), f24::zero(), f24::zero() }); // Dummy register used by the JIT + protected: std::array operandDescriptors; alignas(16) std::array tempRegisters; // General purpose registers the shader can use for temp values @@ -191,15 +203,6 @@ public: std::array loadedShader; // Currently loaded & active shader std::array bufferedShader; // Shader to be transferred when the SH_CODETRANSFER_END reg gets written to - u32 entrypoint = 0; // Initial shader PC - u32 boolUniform; - std::array, 4> intUniforms; - alignas(16) std::array floatUniforms; - - alignas(16) std::array fixedAttributes; // Fixed vertex attributes - alignas(16) std::array inputs; // Attributes passed to the shader - alignas(16) std::array outputs; - PICAShader(ShaderType type) : type(type) {} // Theese functions are in the header to be inlined more easily, though with LTO I hope I'll be able to move them diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index d06b9db2..219eb3d7 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -2,6 +2,7 @@ #include "PICA/dynapica/shader_rec_emitter_x64.hpp" #include +#include #include using namespace Xbyak; @@ -11,7 +12,9 @@ using namespace Xbyak::util; static constexpr Reg64 statePointer = rbp; static constexpr Xmm scratch1 = xmm0; static constexpr Xmm scratch2 = xmm1; -static constexpr Xmm scratch3 = xmm2; +static constexpr Xmm src1_xmm = xmm2; +static constexpr Xmm src2_xmm = xmm3; +static constexpr Xmm src3_xmm = xmm4; void ShaderEmitter::compile(const PICAShader& shaderUnit) { // Emit prologue first @@ -71,15 +74,17 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { const u32 opcode = instruction >> 26; switch (opcode) { + case ShaderOpcodes::ADD: recADD(shaderUnit, instruction); break; + case ShaderOpcodes::DP4: recDP4(shaderUnit, instruction); break; + case ShaderOpcodes::END: recEND(shaderUnit, instruction); break; case ShaderOpcodes::MOV: recMOV(shaderUnit, instruction); break; + case ShaderOpcodes::NOP: break; default: Helpers::panic("Shader JIT: Unimplemented PICA opcode %X", opcode); } } const ShaderEmitter::vec4f& ShaderEmitter::getSourceRef(const PICAShader& shader, u32 src) { - alignas(16) static vec4f dummy = vec4f({ f24::zero(), f24::zero(), f24::zero(), f24::zero() }); - if (src < 0x10) return shader.inputs[src]; else if (src < 0x20) @@ -88,7 +93,7 @@ const ShaderEmitter::vec4f& ShaderEmitter::getSourceRef(const PICAShader& shader return shader.floatUniforms[src - 0x20]; else { Helpers::warn("[Shader JIT] Unimplemented source value: %X\n", src); - return dummy; + return shader.dummy; } } @@ -132,7 +137,7 @@ void ShaderEmitter::loadRegister(Xmm dest, const PICAShader& shader, u32 src, u3 movaps(dest, xword[statePointer + offset]); else // Swizzle is not trivial so we need to emit a shuffle instruction pshufd(dest, xword[statePointer + offset], convertedSwizzle); - return; + break; } default: @@ -142,8 +147,6 @@ void ShaderEmitter::loadRegister(Xmm dest, const PICAShader& shader, u32 src, u3 if (negate) { Helpers::panic("[ShaderJIT] Unimplemented register negation"); } - - Helpers::panic("Reached unreachable path in PICAShader::getIndexedSource"); } void ShaderEmitter::storeRegister(Xmm source, const PICAShader& shader, u32 dest, u32 operandDescriptor) { @@ -151,9 +154,22 @@ void ShaderEmitter::storeRegister(Xmm source, const PICAShader& shader, u32 dest const uintptr_t offset = uintptr_t(&destRef) - uintptr_t(&shader); // Calculate offset of register from start of the state struct // Mask of which lanes to write + // TODO: If only 1 lane is being written to, use movss u32 writeMask = operandDescriptor & 0xf; if (writeMask == 0xf) { // No lanes are masked, just movaps movaps(xword[statePointer + offset], source); + } else if (std::popcount(writeMask) == 1) { // Only 1 register needs to be written back. This can be done with a simple shift right + movss + int bit = std::countr_zero(writeMask); // Get which PICA register needs to be written to (0 = w, 1 = z, etc) + size_t index = 3 - bit; + const uintptr_t lane_offset = offset + index * sizeof(float); + + if (index == 0) { // Bottom lane, no need to shift + movss(dword[statePointer + lane_offset], source); + } else { // Shift right by 32 * index, then write bottom lane + movaps(scratch1, source); + psrldq(scratch1, index * sizeof(float)); + movss(dword[statePointer + lane_offset], scratch1); + } } else if (haveSSE4_1) { // Bit reverse the write mask because that is what blendps expects u32 adjustedMask = ((writeMask >> 3) & 0b1) | ((writeMask >> 1) & 0b10) | ((writeMask << 1) & 0b100) | ((writeMask << 3) & 0b1000); @@ -176,14 +192,53 @@ void ShaderEmitter::storeRegister(Xmm source, const PICAShader& shader, u32 dest } } +void ShaderEmitter::recEND(const PICAShader& shader, u32 instruction) { + // Undo anything the prologue did and return + // Dellocate shadow stack on Windows + if constexpr (isWindows()) { + add(rsp, 32); + } + + // Restore registers + pop(statePointer); + ret(); +} + void ShaderEmitter::recMOV(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; u32 src = (instruction >> 12) & 0x7f; const u32 idx = (instruction >> 19) & 3; const u32 dest = (instruction >> 21) & 0x1f; - loadRegister<1>(scratch1, shader, src, idx, operandDescriptor); // Load source 1 into scratch1 - storeRegister(scratch1, shader, dest, operandDescriptor); + loadRegister<1>(src1_xmm, shader, src, idx, operandDescriptor); // Load source 1 into scratch1 + storeRegister(src1_xmm, shader, dest, operandDescriptor); +} + +void ShaderEmitter::recADD(const PICAShader& shader, u32 instruction) { + const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; + u32 src1 = (instruction >> 12) & 0x7f; + const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment + const u32 idx = (instruction >> 19) & 3; + const u32 dest = (instruction >> 21) & 0x1f; + + loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor); + loadRegister<2>(src2_xmm, shader, src2, 0, operandDescriptor); + addps(src1_xmm, src2_xmm); // Dot product between the 2 register + storeRegister(src1_xmm, shader, dest, operandDescriptor); +} + +void ShaderEmitter::recDP4(const PICAShader& shader, u32 instruction) { + const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; + u32 src1 = (instruction >> 12) & 0x7f; + const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment + const u32 idx = (instruction >> 19) & 3; + const u32 dest = (instruction >> 21) & 0x1f; + + // TODO: Safe multiplication equivalent (Multiplication is not IEEE compliant on the PICA) + loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor); + loadRegister<2>(src2_xmm, shader, src2, 0, operandDescriptor); + dpps(src1_xmm, src2_xmm, 0b11111111); // Dot product between the 2 register, store the result in all lanes of scratch1 similarly to PICA + storeRegister(src1_xmm, shader, dest, operandDescriptor); } #endif \ No newline at end of file From ca3ae7a16e8702572724977aaea6993a3cf2d07e Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Fri, 9 Jun 2023 17:14:04 +0300 Subject: [PATCH 016/103] [Shader JIT] Add more function definitions --- .../PICA/dynapica/shader_rec_emitter_x64.hpp | 21 +++++++++++++++++++ .../PICA/dynapica/shader_rec_emitter_x64.cpp | 1 - 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/include/PICA/dynapica/shader_rec_emitter_x64.hpp b/include/PICA/dynapica/shader_rec_emitter_x64.hpp index 27d1865f..4295578f 100644 --- a/include/PICA/dynapica/shader_rec_emitter_x64.hpp +++ b/include/PICA/dynapica/shader_rec_emitter_x64.hpp @@ -52,9 +52,30 @@ class ShaderEmitter : public Xbyak::CodeGenerator { // Instruction recompilation functions void recADD(const PICAShader& shader, u32 instruction); + void recCALL(const PICAShader& shader, u32 instruction); + void recCALLC(const PICAShader& shader, u32 instruction); + void recCALLU(const PICAShader& shader, u32 instruction); + void recCMP(const PICAShader& shader, u32 instruction); + void recDP3(const PICAShader& shader, u32 instruction); void recDP4(const PICAShader& shader, u32 instruction); void recEND(const PICAShader& shader, u32 instruction); + void recFLR(const PICAShader& shader, u32 instruction); + void recIFC(const PICAShader& shader, u32 instruction); + void recIFU(const PICAShader& shader, u32 instruction); + void recJMPC(const PICAShader& shader, u32 instruction); + void recJMPU(const PICAShader& shader, u32 instruction); + void recLOOP(const PICAShader& shader, u32 instruction); + void recMAX(const PICAShader& shader, u32 instruction); + void recMIN(const PICAShader& shader, u32 instruction); + void recMOVA(const PICAShader& shader, u32 instruction); void recMOV(const PICAShader& shader, u32 instruction); + void recMUL(const PICAShader& shader, u32 instruction); + void recRCP(const PICAShader& shader, u32 instruction); + void recRSQ(const PICAShader& shader, u32 instruction); + void recSGE(const PICAShader& shader, u32 instruction); + void recSGEI(const PICAShader& shader, u32 instruction); + void recSLT(const PICAShader& shader, u32 instruction); + void recSLTI(const PICAShader& shader, u32 instruction); public: using InstructionCallback = const void(*)(PICAShader& shaderUnit); // Callback type used for instructions diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 219eb3d7..bbf835e6 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -154,7 +154,6 @@ void ShaderEmitter::storeRegister(Xmm source, const PICAShader& shader, u32 dest const uintptr_t offset = uintptr_t(&destRef) - uintptr_t(&shader); // Calculate offset of register from start of the state struct // Mask of which lanes to write - // TODO: If only 1 lane is being written to, use movss u32 writeMask = operandDescriptor & 0xf; if (writeMask == 0xf) { // No lanes are masked, just movaps movaps(xword[statePointer + offset], source); From 1fe44c009860632072901db81489e122077f3f7f Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Fri, 9 Jun 2023 22:31:06 +0300 Subject: [PATCH 017/103] [ShaderJIT] Add cmp --- .../PICA/dynapica/shader_rec_emitter_x64.cpp | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index bbf835e6..55bef778 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -3,6 +3,7 @@ #include #include +#include #include using namespace Xbyak; @@ -75,6 +76,9 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { switch (opcode) { case ShaderOpcodes::ADD: recADD(shaderUnit, instruction); break; + case ShaderOpcodes::CMP1: case ShaderOpcodes::CMP2: + recCMP(shaderUnit, instruction); + break; case ShaderOpcodes::DP4: recDP4(shaderUnit, instruction); break; case ShaderOpcodes::END: recEND(shaderUnit, instruction); break; case ShaderOpcodes::MOV: recMOV(shaderUnit, instruction); break; @@ -240,4 +244,71 @@ void ShaderEmitter::recDP4(const PICAShader& shader, u32 instruction) { storeRegister(src1_xmm, shader, dest, operandDescriptor); } +void ShaderEmitter::recCMP(const PICAShader& shader, u32 instruction) { + const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; + const u32 src1 = (instruction >> 12) & 0x7f; + const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment + const u32 idx = (instruction >> 19) & 3; + const u32 cmpY = (instruction >> 21) & 7; + const u32 cmpX = (instruction >> 24) & 7; + + loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor); + loadRegister<2>(src2_xmm, shader, src2, 0, operandDescriptor); + + // Condition codes for cmpps + enum : u8 { + CMP_EQ = 0, + CMP_LT = 1, + CMP_LE = 2, + CMP_UNORD = 3, + CMP_NEQ = 4, + CMP_NLT = 5, + CMP_NLE = 6, + CMP_ORD = 7, + CMP_TRUE = 15 + }; + + // Map from PICA condition codes (used as index) to x86 condition codes + static constexpr std::array conditionCodes = { CMP_EQ, CMP_NEQ, CMP_LT, CMP_LE, CMP_LT, CMP_LE, CMP_TRUE, CMP_TRUE }; + + // SSE does not offer GT or GE comparisons in the cmpps instruction, so we need to flip the left and right operands in that case and use LT/LE + const bool invertX = (cmpX == 4 || cmpX == 5); + const bool invertY = (cmpY == 4 || cmpY == 5); + Xmm lhs_x = invertX ? src2_xmm : src1_xmm; + Xmm rhs_x = invertX ? src1_xmm : src2_xmm; + Xmm lhs_y = invertY ? src2_xmm : src1_xmm; + Xmm rhs_y = invertY ? src1_xmm : src2_xmm; + + const u8 compareFuncX = conditionCodes[cmpX]; + const u8 compareFuncY = conditionCodes[cmpY]; + + static_assert(sizeof(bool) == 1 && sizeof(shader.cmpRegister) == 2); // The code below relies on bool being 1 byte exactly + const size_t cmpRegXOffset = uintptr_t(&shader.cmpRegister) - uintptr_t(&shader); + const size_t cmpRegYOffset = cmpRegXOffset + 1; + + // Cmp x and y are the same compare function, we can use a single cmp instruction + if (cmpX == cmpY) { + cmpps(lhs_x, rhs_x, compareFuncX); + movd(eax, lhs_x); + test(eax, eax); + + setne(byte[statePointer + cmpRegXOffset]); + setne(byte[statePointer + cmpRegYOffset]); + } else { + movaps(scratch1, lhs_x); // Copy the left hand operands to temp registers + movaps(scratch2, lhs_y); + + cmpps(scratch1, rhs_x, compareFuncX); // Perform the compares + cmpps(scratch2, rhs_y, compareFuncY); + + movd(eax, scratch1); // Move results to eax for X and edx for Y + movd(edx, scratch2); + + test(eax, eax); // Write back results with setne + setne(byte[statePointer + cmpRegXOffset]); + test(edx, edx); + setne(byte[statePointer + cmpRegYOffset]); + } +} + #endif \ No newline at end of file From 1aea8199c5e97e2fbcc2a888132967dd7b355f73 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 10 Jun 2023 00:58:38 +0300 Subject: [PATCH 018/103] [ShaderJIT] CALL, DP4, IFC, IFU, MUL, RSQ --- .../PICA/dynapica/shader_rec_emitter_x64.hpp | 8 + .../PICA/dynapica/shader_rec_emitter_x64.cpp | 173 +++++++++++++++++- src/main.cpp | 2 +- 3 files changed, 180 insertions(+), 3 deletions(-) diff --git a/include/PICA/dynapica/shader_rec_emitter_x64.hpp b/include/PICA/dynapica/shader_rec_emitter_x64.hpp index 4295578f..c99d67e5 100644 --- a/include/PICA/dynapica/shader_rec_emitter_x64.hpp +++ b/include/PICA/dynapica/shader_rec_emitter_x64.hpp @@ -50,6 +50,14 @@ class ShaderEmitter : public Xbyak::CodeGenerator { const vec4f& getSourceRef(const PICAShader& shader, u32 src); const vec4f& getDestRef(const PICAShader& shader, u32 dest); + // Check the value of the cmp register for instructions like ifc and callc + // Result is returned in the zero flag. If the comparison is true then zero == 1, else zero == 0 + void checkCmpRegister(const PICAShader& shader, u32 instruction); + + // Check the value of the bool uniform for instructions like ifu and callu + // Result is returned in the zero flag. If the comparison is true then zero == 0, else zero == 1 (Opposite of checkCmpRegister) + void checkBoolUniform(const PICAShader& shader, u32 instruction); + // Instruction recompilation functions void recADD(const PICAShader& shader, u32 instruction); void recCALL(const PICAShader& shader, u32 instruction); diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 55bef778..3d7f781c 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -59,6 +59,9 @@ void ShaderEmitter::scanForCalls(const PICAShader& shaderUnit) { returnPCs.push_back(returnPC); } } + + // Sort return PCs so they can be binary searched + std::sort(returnPCs.begin(), returnPCs.end()); } void ShaderEmitter::compileUntil(const PICAShader& shaderUnit, u32 end) { @@ -70,19 +73,33 @@ void ShaderEmitter::compileUntil(const PICAShader& shaderUnit, u32 end) { void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { // Write current location to label for this instruction L(instructionLabels[recompilerPC]); + + // See if PC is a possible return PC and emit the proper code if so + if (std::binary_search(returnPCs.begin(), returnPCs.end(), recompilerPC)) { + int3(); + } + // Fetch instruction and inc PC const u32 instruction = shaderUnit.loadedShader[recompilerPC++]; const u32 opcode = instruction >> 26; switch (opcode) { case ShaderOpcodes::ADD: recADD(shaderUnit, instruction); break; + case ShaderOpcodes::CALL: + recCALL(shaderUnit, instruction); + break; case ShaderOpcodes::CMP1: case ShaderOpcodes::CMP2: recCMP(shaderUnit, instruction); break; + case ShaderOpcodes::DP3: recDP3(shaderUnit, instruction); break; case ShaderOpcodes::DP4: recDP4(shaderUnit, instruction); break; case ShaderOpcodes::END: recEND(shaderUnit, instruction); break; + case ShaderOpcodes::IFC: recIFC(shaderUnit, instruction); break; + case ShaderOpcodes::IFU: recIFU(shaderUnit, instruction); break; case ShaderOpcodes::MOV: recMOV(shaderUnit, instruction); break; + case ShaderOpcodes::MUL: recMUL(shaderUnit, instruction); break; case ShaderOpcodes::NOP: break; + case ShaderOpcodes::RSQ: recRSQ(shaderUnit, instruction); break; default: Helpers::panic("Shader JIT: Unimplemented PICA opcode %X", opcode); } @@ -195,6 +212,46 @@ void ShaderEmitter::storeRegister(Xmm source, const PICAShader& shader, u32 dest } } +void ShaderEmitter::checkCmpRegister(const PICAShader& shader, u32 instruction) { + static_assert(sizeof(bool) == 1 && sizeof(shader.cmpRegister) == 2); // The code below relies on bool being 1 byte exactly + const size_t cmpRegXOffset = uintptr_t(&shader.cmpRegister) - uintptr_t(&shader); + const size_t cmpRegYOffset = cmpRegXOffset + sizeof(bool); + + const u32 condition = (instruction >> 22) & 3; + const uint refY = (instruction >> 24) & 1; + const uint refX = (instruction >> 25) & 1; + + // refX in the bottom byte, refY in the top byte. This is done for condition codes 0 and 1 which check both x and y, so we can emit a single instruction that checks both + const u16 refX_refY_merged = refX | (refY << 8); + + switch (condition) { + case 0: // Either cmp register matches + // Z flag is 0 if at least 1 of them is set + test(word[statePointer + cmpRegXOffset], refX_refY_merged); + + // Invert z flag + setz(al); + test(al, al); + break; + case 1: // Both cmp registers match + cmp(word[statePointer + cmpRegXOffset], refX_refY_merged); + break; + case 2: // At least cmp.x matches + cmp(byte[statePointer + cmpRegXOffset], refX); + break; + default: // At least cmp.y matches + cmp(byte[statePointer + cmpRegYOffset], refY); + break; + } +} + +void ShaderEmitter::checkBoolUniform(const PICAShader& shader, u32 instruction) { + const u32 bit = (instruction >> 22) & 0xf; // Bit of the bool uniform to check + const uintptr_t boolUniformOffset = uintptr_t(&shader.boolUniform) - uintptr_t(&shader); + + test(word[statePointer + boolUniformOffset], 1 << bit); +} + void ShaderEmitter::recEND(const PICAShader& shader, u32 instruction) { // Undo anything the prologue did and return // Dellocate shadow stack on Windows @@ -230,6 +287,20 @@ void ShaderEmitter::recADD(const PICAShader& shader, u32 instruction) { storeRegister(src1_xmm, shader, dest, operandDescriptor); } +void ShaderEmitter::recDP3(const PICAShader& shader, u32 instruction) { + const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; + u32 src1 = (instruction >> 12) & 0x7f; + const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment + const u32 idx = (instruction >> 19) & 3; + const u32 dest = (instruction >> 21) & 0x1f; + + // TODO: Safe multiplication equivalent (Multiplication is not IEEE compliant on the PICA) + loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor); + loadRegister<2>(src2_xmm, shader, src2, 0, operandDescriptor); + dpps(src1_xmm, src2_xmm, 0b11111111); // 3-lane dot product between the 2 registers, store the result in all lanes of scratch1 similarly to PICA + storeRegister(src1_xmm, shader, dest, operandDescriptor); +} + void ShaderEmitter::recDP4(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; u32 src1 = (instruction >> 12) & 0x7f; @@ -240,7 +311,40 @@ void ShaderEmitter::recDP4(const PICAShader& shader, u32 instruction) { // TODO: Safe multiplication equivalent (Multiplication is not IEEE compliant on the PICA) loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor); loadRegister<2>(src2_xmm, shader, src2, 0, operandDescriptor); - dpps(src1_xmm, src2_xmm, 0b11111111); // Dot product between the 2 register, store the result in all lanes of scratch1 similarly to PICA + dpps(src1_xmm, src2_xmm, 0b01111111); // 4-lane dot product between the 2 registers, store the result in all lanes of scratch1 similarly to PICA + storeRegister(src1_xmm, shader, dest, operandDescriptor); +} + +void ShaderEmitter::recMUL(const PICAShader& shader, u32 instruction) { + const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; + u32 src1 = (instruction >> 12) & 0x7f; + const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment + const u32 idx = (instruction >> 19) & 3; + const u32 dest = (instruction >> 21) & 0x1f; + + // TODO: Safe multiplication equivalent (Multiplication is not IEEE compliant on the PICA) + loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor); + loadRegister<2>(src2_xmm, shader, src2, 0, operandDescriptor); + mulps(src1_xmm, src2_xmm); + storeRegister(src1_xmm, shader, dest, operandDescriptor); +} + +void ShaderEmitter::recRSQ(const PICAShader& shader, u32 instruction) { + const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; + u32 src = (instruction >> 12) & 0x7f; + const u32 idx = (instruction >> 19) & 3; + const u32 dest = (instruction >> 21) & 0x1f; + const u32 writeMask = operandDescriptor & 0xf; + + loadRegister<1>(src1_xmm, shader, src, idx, operandDescriptor); // Load source 1 into scratch1 + rsqrtss(src1_xmm, src1_xmm); // Compute rsqrt approximation + + // If we only write back the x component to the result, we needn't perform a shuffle to do res = res.xxxx + // Otherwise we do + if (writeMask != 0x8) {// Copy bottom lane to all lanes if we're not simply writing back x + shufps(src1_xmm, src1_xmm, 0); // src1_xmm = src1_xmm.xxxx + } + storeRegister(src1_xmm, shader, dest, operandDescriptor); } @@ -284,7 +388,7 @@ void ShaderEmitter::recCMP(const PICAShader& shader, u32 instruction) { static_assert(sizeof(bool) == 1 && sizeof(shader.cmpRegister) == 2); // The code below relies on bool being 1 byte exactly const size_t cmpRegXOffset = uintptr_t(&shader.cmpRegister) - uintptr_t(&shader); - const size_t cmpRegYOffset = cmpRegXOffset + 1; + const size_t cmpRegYOffset = cmpRegXOffset + sizeof(bool); // Cmp x and y are the same compare function, we can use a single cmp instruction if (cmpX == cmpY) { @@ -311,4 +415,69 @@ void ShaderEmitter::recCMP(const PICAShader& shader, u32 instruction) { } } +void ShaderEmitter::recIFC(const PICAShader& shader, u32 instruction) { + // z is 1 if true, else 0 + checkCmpRegister(shader, instruction); + const u32 dest = (instruction >> 10) & 0xfff; + const u32 num = instruction & 0xff; + + if (dest < recompilerPC) { + Helpers::warn("Shader JIT: IFC instruction with dest < current PC\n"); + } + Label elseBlock, endIf; + + // Jump to else block if z is 0 + jnz(elseBlock); + compileUntil(shader, dest); + + if (num == 0) { // Else block is empty, + L(elseBlock); + } else { // Else block is NOT empty + jmp(endIf); + L(elseBlock); + compileUntil(shader, dest + num); + L(endIf); + } +} + +void ShaderEmitter::recIFU(const PICAShader& shader, u32 instruction) { + // z is 0 if true, else 1 + checkBoolUniform(shader, instruction); + const u32 dest = (instruction >> 10) & 0xfff; + const u32 num = instruction & 0xff; + + if (dest < recompilerPC) { + Helpers::warn("Shader JIT: IFC instruction with dest < current PC\n"); + } + Label elseBlock, endIf; + + // Jump to else block if z is 1 + jz(elseBlock, T_NEAR); + compileUntil(shader, dest); + + if (num == 0) { // Else block is empty, + L(elseBlock); + } + else { // Else block is NOT empty + jmp(endIf, T_NEAR); + L(elseBlock); + compileUntil(shader, dest + num); + L(endIf); + } +} + +void ShaderEmitter::recCALL(const PICAShader& shader, u32 instruction) { + const u32 dest = (instruction >> 10) & 0xfff; + const u32 num = instruction & 0xff; + + // Push return PC as stack parameter. This is a decently fast solution and Citra does the same but we should probably switch to a proper PICA-like + // Callstack, because it's not great to have an infinitely expanding call stack where popping from empty stack is undefined as hell + push(qword, dest + num); + // Realign stack to 64 bits and allocate shadow space on windows + sub(rsp, isWindows() ? (8 + 32) : 8); + + // Call subroutine, Xbyak will update the label if it hasn't been initialized yet + call(instructionLabels[dest]); +} + #endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 24fdc9f8..d17f8a69 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,7 +9,7 @@ int main (int argc, char *argv[]) { emu.initGraphicsContext(); - auto romPath = std::filesystem::current_path() / (argc > 1 ? argv[1] : "Metroid Prime - Federation Force (Europe) (En,Fr,De,Es,It).3ds"); + auto romPath = std::filesystem::current_path() / (argc > 1 ? argv[1] : "OoT Demo.3ds"); if (!emu.loadROM(romPath)) { // For some reason just .c_str() doesn't show the proper path Helpers::panic("Failed to load ROM file: %s", romPath.string().c_str()); From 2cb249927e751998c6fdcc74d8df12e7a9b25968 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 10 Jun 2023 01:28:28 +0300 Subject: [PATCH 019/103] [ShaderJIT] FIx crashes, moar instructions --- .../PICA/dynapica/shader_rec_emitter_x64.hpp | 5 ++ .../PICA/dynapica/shader_rec_emitter_x64.cpp | 72 +++++++++++++++++-- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/include/PICA/dynapica/shader_rec_emitter_x64.hpp b/include/PICA/dynapica/shader_rec_emitter_x64.hpp index c99d67e5..a253b9f9 100644 --- a/include/PICA/dynapica/shader_rec_emitter_x64.hpp +++ b/include/PICA/dynapica/shader_rec_emitter_x64.hpp @@ -27,6 +27,9 @@ class ShaderEmitter : public Xbyak::CodeGenerator { // Filled before compiling a shader by scanning the code for call instructions std::vector returnPCs; + // Vector value of (-0.0, -0.0, -0.0, -0.0) for negating vectors via pxor + Label negateVector; + u32 recompilerPC = 0; // PC the recompiler is currently recompiling @ bool haveSSE4_1 = false; // Shows if the CPU supports SSE4.1 @@ -73,6 +76,8 @@ class ShaderEmitter : public Xbyak::CodeGenerator { void recJMPC(const PICAShader& shader, u32 instruction); void recJMPU(const PICAShader& shader, u32 instruction); void recLOOP(const PICAShader& shader, u32 instruction); + void recMAD(const PICAShader& shader, u32 instruction); + void recMADI(const PICAShader& shader, u32 instruction); void recMAX(const PICAShader& shader, u32 instruction); void recMIN(const PICAShader& shader, u32 instruction); void recMOVA(const PICAShader& shader, u32 instruction); diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 3d7f781c..78ee469f 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -18,6 +18,11 @@ static constexpr Xmm src2_xmm = xmm3; static constexpr Xmm src3_xmm = xmm4; void ShaderEmitter::compile(const PICAShader& shaderUnit) { + // Constants + align(16); + L(negateVector); + dd(0x80000000); dd(0x80000000); dd(0x80000000); dd(0x80000000); // -0.0 4 times + // Emit prologue first align(16); prologueCb = getCurr(); @@ -97,9 +102,15 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { case ShaderOpcodes::IFC: recIFC(shaderUnit, instruction); break; case ShaderOpcodes::IFU: recIFU(shaderUnit, instruction); break; case ShaderOpcodes::MOV: recMOV(shaderUnit, instruction); break; + case ShaderOpcodes::MAX: recMAX(shaderUnit, instruction); break; case ShaderOpcodes::MUL: recMUL(shaderUnit, instruction); break; case ShaderOpcodes::NOP: break; + case ShaderOpcodes::RCP: recRCP(shaderUnit, instruction); break; case ShaderOpcodes::RSQ: recRSQ(shaderUnit, instruction); break; + + case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: + recMAD(shaderUnit, instruction); + break; default: Helpers::panic("Shader JIT: Unimplemented PICA opcode %X", opcode); } @@ -166,7 +177,7 @@ void ShaderEmitter::loadRegister(Xmm dest, const PICAShader& shader, u32 src, u3 } if (negate) { - Helpers::panic("[ShaderJIT] Unimplemented register negation"); + pxor(dest, xword[rip + negateVector]); } } @@ -297,7 +308,7 @@ void ShaderEmitter::recDP3(const PICAShader& shader, u32 instruction) { // TODO: Safe multiplication equivalent (Multiplication is not IEEE compliant on the PICA) loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor); loadRegister<2>(src2_xmm, shader, src2, 0, operandDescriptor); - dpps(src1_xmm, src2_xmm, 0b11111111); // 3-lane dot product between the 2 registers, store the result in all lanes of scratch1 similarly to PICA + dpps(src1_xmm, src2_xmm, 0b01111111); // 3-lane dot product between the 2 registers, store the result in all lanes of scratch1 similarly to PICA storeRegister(src1_xmm, shader, dest, operandDescriptor); } @@ -311,7 +322,20 @@ void ShaderEmitter::recDP4(const PICAShader& shader, u32 instruction) { // TODO: Safe multiplication equivalent (Multiplication is not IEEE compliant on the PICA) loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor); loadRegister<2>(src2_xmm, shader, src2, 0, operandDescriptor); - dpps(src1_xmm, src2_xmm, 0b01111111); // 4-lane dot product between the 2 registers, store the result in all lanes of scratch1 similarly to PICA + dpps(src1_xmm, src2_xmm, 0b11111111); // 4-lane dot product between the 2 registers, store the result in all lanes of scratch1 similarly to PICA + storeRegister(src1_xmm, shader, dest, operandDescriptor); +} + +void ShaderEmitter::recMAX(const PICAShader& shader, u32 instruction) { + const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; + u32 src1 = (instruction >> 12) & 0x7f; + const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment + const u32 idx = (instruction >> 19) & 3; + const u32 dest = (instruction >> 21) & 0x1f; + + loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor); + loadRegister<2>(src2_xmm, shader, src2, 0, operandDescriptor); + maxps(src1_xmm, src2_xmm); storeRegister(src1_xmm, shader, dest, operandDescriptor); } @@ -329,6 +353,25 @@ void ShaderEmitter::recMUL(const PICAShader& shader, u32 instruction) { storeRegister(src1_xmm, shader, dest, operandDescriptor); } +void ShaderEmitter::recRCP(const PICAShader& shader, u32 instruction) { + const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; + u32 src = (instruction >> 12) & 0x7f; + const u32 idx = (instruction >> 19) & 3; + const u32 dest = (instruction >> 21) & 0x1f; + const u32 writeMask = operandDescriptor & 0xf; + + loadRegister<1>(src1_xmm, shader, src, idx, operandDescriptor); // Load source 1 into scratch1 + rcpss(src1_xmm, src1_xmm); // Compute rcp approximation + + // If we only write back the x component to the result, we needn't perform a shuffle to do res = res.xxxx + // Otherwise we do + if (writeMask != 0x8) {// Copy bottom lane to all lanes if we're not simply writing back x + shufps(src1_xmm, src1_xmm, 0); // src1_xmm = src1_xmm.xxxx + } + + storeRegister(src1_xmm, shader, dest, operandDescriptor); +} + void ShaderEmitter::recRSQ(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; u32 src = (instruction >> 12) & 0x7f; @@ -348,6 +391,25 @@ void ShaderEmitter::recRSQ(const PICAShader& shader, u32 instruction) { storeRegister(src1_xmm, shader, dest, operandDescriptor); } +void ShaderEmitter::recMAD(const PICAShader& shader, u32 instruction) { + const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x1f]; + const u32 src1 = (instruction >> 17) & 0x1f; + u32 src2 = (instruction >> 10) & 0x7f; + const u32 src3 = (instruction >> 5) & 0x1f; + const u32 idx = (instruction >> 22) & 3; + const u32 dest = (instruction >> 24) & 0x1f; + + loadRegister<1>(src1_xmm, shader, src1, 0, operandDescriptor); + loadRegister<2>(src2_xmm, shader, src2, idx, operandDescriptor); + loadRegister<3>(src3_xmm, shader, src3, 0, operandDescriptor); + + movaps(scratch1, src1_xmm); + // TODO: Implement safe PICA mul + mulps(scratch1, src2_xmm); + addps(scratch1, src3_xmm); + storeRegister(scratch1, shader, dest, operandDescriptor); +} + void ShaderEmitter::recCMP(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; const u32 src1 = (instruction >> 12) & 0x7f; @@ -427,13 +489,13 @@ void ShaderEmitter::recIFC(const PICAShader& shader, u32 instruction) { Label elseBlock, endIf; // Jump to else block if z is 0 - jnz(elseBlock); + jnz(elseBlock, T_NEAR); compileUntil(shader, dest); if (num == 0) { // Else block is empty, L(elseBlock); } else { // Else block is NOT empty - jmp(endIf); + jmp(endIf, T_NEAR); L(elseBlock); compileUntil(shader, dest + num); L(endIf); From b04ad7215b54a1f9906d310e80eeef4b9608768b Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 10 Jun 2023 13:22:34 +0300 Subject: [PATCH 020/103] Clean up some getBits code --- include/PICA/shader.hpp | 21 +++++++++++-------- .../PICA/dynapica/shader_rec_emitter_x64.cpp | 2 ++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/include/PICA/shader.hpp b/include/PICA/shader.hpp index c9f74f8b..94e055ba 100644 --- a/include/PICA/shader.hpp +++ b/include/PICA/shader.hpp @@ -157,15 +157,16 @@ private: u32 compSwizzle; bool negate; + using namespace Helpers; if constexpr (sourceIndex == 1) { // SRC1 - negate = (Helpers::getBit<4>(opDescriptor)) != 0; - compSwizzle = Helpers::getBits<5, 8>(opDescriptor); + negate = (getBit<4>(opDescriptor)) != 0; + compSwizzle = getBits<5, 8>(opDescriptor); } else if constexpr (sourceIndex == 2) { // SRC2 - negate = (Helpers::getBit<13>(opDescriptor)) != 0; - compSwizzle = Helpers::getBits<14, 8>(opDescriptor); + negate = (getBit<13>(opDescriptor)) != 0; + compSwizzle = getBits<14, 8>(opDescriptor); } else if constexpr (sourceIndex == 3) { // SRC3 - negate = (Helpers::getBit<22>(opDescriptor)) != 0; - compSwizzle = Helpers::getBits<23, 8>(opDescriptor); + negate = (getBit<22>(opDescriptor)) != 0; + compSwizzle = getBits<23, 8>(opDescriptor); } // Iterate through every component of the swizzled vector in reverse order @@ -263,11 +264,13 @@ public: } void uploadIntUniform(int index, u32 word) { + using namespace Helpers; + auto& u = intUniforms[index]; u.x() = word & 0xff; - u.y() = Helpers::getBits<8, 8>(word); - u.z() = Helpers::getBits<16, 8>(word); - u.w() = Helpers::getBits<24, 8>(word); + u.y() = getBits<8, 8>(word); + u.z() = getBits<16, 8>(word); + u.w() = getBits<24, 8>(word); } void run(); diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 78ee469f..9c00387c 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -8,6 +8,8 @@ using namespace Xbyak; using namespace Xbyak::util; +using Helpers::getBit; +using Helpers::getBits; // Register that points to PICA state static constexpr Reg64 statePointer = rbp; From df414acc2322ae4260a1a11d9663bae3be88a800 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 10 Jun 2023 14:04:15 +0300 Subject: [PATCH 021/103] [ShaderJIT] Migrate to #18 --- .../PICA/dynapica/shader_rec_emitter_x64.cpp | 143 +++++++++--------- 1 file changed, 71 insertions(+), 72 deletions(-) diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 9c00387c..a2ddc9c1 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -8,8 +8,7 @@ using namespace Xbyak; using namespace Xbyak::util; -using Helpers::getBit; -using Helpers::getBits; +using namespace Helpers; // Register that points to PICA state static constexpr Reg64 statePointer = rbp; @@ -59,8 +58,8 @@ void ShaderEmitter::scanForCalls(const PICAShader& shaderUnit) { for (u32 i = 0; i < PICAShader::maxInstructionCount; i++) { const u32 instruction = shaderUnit.loadedShader[i]; if (isCall(instruction)) { - const u32 num = instruction & 0xff; // Num of instructions to execute - const u32 dest = (instruction >> 10) & 0xfff; // Starting subroutine address + const u32 num = instruction & 0xff; + const u32 dest = getBits<10, 12>(instruction); const u32 returnPC = num + dest; // Add them to get the return PC returnPCs.push_back(returnPC); @@ -147,16 +146,16 @@ void ShaderEmitter::loadRegister(Xmm dest, const PICAShader& shader, u32 src, u3 bool negate; // If true, negate all lanes of the register if constexpr (sourceIndex == 1) { // SRC1 - negate = ((operandDescriptor >> 4) & 1) != 0; - compSwizzle = (operandDescriptor >> 5) & 0xff; + negate = (getBit<4>(operandDescriptor)) != 0; + compSwizzle = getBits<5, 8>(operandDescriptor); } else if constexpr (sourceIndex == 2) { // SRC2 - negate = ((operandDescriptor >> 13) & 1) != 0; - compSwizzle = (operandDescriptor >> 14) & 0xff; + negate = (getBit<13>(operandDescriptor)) != 0; + compSwizzle = getBits<14, 8>(operandDescriptor); } else if constexpr (sourceIndex == 3) { // SRC3 - negate = ((operandDescriptor >> 22) & 1) != 0; - compSwizzle = (operandDescriptor >> 23) & 0xff; + negate = (getBit<22>(operandDescriptor)) != 0; + compSwizzle = getBits<23, 8>(operandDescriptor); } // PICA has the swizzle descriptor inverted in comparison to x86. For the PICA, the descriptor is (lowest to highest bits) wzyx while it's xyzw for x86 @@ -230,36 +229,36 @@ void ShaderEmitter::checkCmpRegister(const PICAShader& shader, u32 instruction) const size_t cmpRegXOffset = uintptr_t(&shader.cmpRegister) - uintptr_t(&shader); const size_t cmpRegYOffset = cmpRegXOffset + sizeof(bool); - const u32 condition = (instruction >> 22) & 3; - const uint refY = (instruction >> 24) & 1; - const uint refX = (instruction >> 25) & 1; + const u32 condition = getBits<22, 2>(instruction); + const uint refY = getBit<24>(instruction); + const uint refX = getBit<25>(instruction); // refX in the bottom byte, refY in the top byte. This is done for condition codes 0 and 1 which check both x and y, so we can emit a single instruction that checks both const u16 refX_refY_merged = refX | (refY << 8); switch (condition) { - case 0: // Either cmp register matches - // Z flag is 0 if at least 1 of them is set - test(word[statePointer + cmpRegXOffset], refX_refY_merged); + case 0: // Either cmp register matches + // Z flag is 0 if at least 1 of them is set + test(word[statePointer + cmpRegXOffset], refX_refY_merged); - // Invert z flag - setz(al); - test(al, al); - break; - case 1: // Both cmp registers match - cmp(word[statePointer + cmpRegXOffset], refX_refY_merged); - break; - case 2: // At least cmp.x matches - cmp(byte[statePointer + cmpRegXOffset], refX); - break; - default: // At least cmp.y matches - cmp(byte[statePointer + cmpRegYOffset], refY); - break; + // Invert z flag + setz(al); + test(al, al); + break; + case 1: // Both cmp registers match + cmp(word[statePointer + cmpRegXOffset], refX_refY_merged); + break; + case 2: // At least cmp.x matches + cmp(byte[statePointer + cmpRegXOffset], refX); + break; + default: // At least cmp.y matches + cmp(byte[statePointer + cmpRegYOffset], refY); + break; } } void ShaderEmitter::checkBoolUniform(const PICAShader& shader, u32 instruction) { - const u32 bit = (instruction >> 22) & 0xf; // Bit of the bool uniform to check + const u32 bit = getBits<22, 4>(instruction); // Bit of the bool uniform to check const uintptr_t boolUniformOffset = uintptr_t(&shader.boolUniform) - uintptr_t(&shader); test(word[statePointer + boolUniformOffset], 1 << bit); @@ -279,9 +278,9 @@ void ShaderEmitter::recEND(const PICAShader& shader, u32 instruction) { void ShaderEmitter::recMOV(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; - u32 src = (instruction >> 12) & 0x7f; - const u32 idx = (instruction >> 19) & 3; - const u32 dest = (instruction >> 21) & 0x1f; + const u32 src = getBits<12, 7>(instruction); + const u32 idx = getBits<19, 2>(instruction); + const u32 dest = getBits<21, 5>(instruction); loadRegister<1>(src1_xmm, shader, src, idx, operandDescriptor); // Load source 1 into scratch1 storeRegister(src1_xmm, shader, dest, operandDescriptor); @@ -289,10 +288,10 @@ void ShaderEmitter::recMOV(const PICAShader& shader, u32 instruction) { void ShaderEmitter::recADD(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; - u32 src1 = (instruction >> 12) & 0x7f; - const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment - const u32 idx = (instruction >> 19) & 3; - const u32 dest = (instruction >> 21) & 0x1f; + const u32 src1 = getBits<12, 7>(instruction); + const u32 src2 = getBits<7, 5>(instruction); // src2 coming first because PICA moment + const u32 idx = getBits<19, 2>(instruction); + const u32 dest = getBits<21, 5>(instruction); loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor); loadRegister<2>(src2_xmm, shader, src2, 0, operandDescriptor); @@ -302,10 +301,10 @@ void ShaderEmitter::recADD(const PICAShader& shader, u32 instruction) { void ShaderEmitter::recDP3(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; - u32 src1 = (instruction >> 12) & 0x7f; - const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment - const u32 idx = (instruction >> 19) & 3; - const u32 dest = (instruction >> 21) & 0x1f; + const u32 src1 = getBits<12, 7>(instruction); + const u32 src2 = getBits<7, 5>(instruction); // src2 coming first because PICA moment + const u32 idx = getBits<19, 2>(instruction); + const u32 dest = getBits<21, 5>(instruction); // TODO: Safe multiplication equivalent (Multiplication is not IEEE compliant on the PICA) loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor); @@ -316,10 +315,10 @@ void ShaderEmitter::recDP3(const PICAShader& shader, u32 instruction) { void ShaderEmitter::recDP4(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; - u32 src1 = (instruction >> 12) & 0x7f; - const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment - const u32 idx = (instruction >> 19) & 3; - const u32 dest = (instruction >> 21) & 0x1f; + const u32 src1 = getBits<12, 7>(instruction); + const u32 src2 = getBits<7, 5>(instruction); // src2 coming first because PICA moment + const u32 idx = getBits<19, 2>(instruction); + const u32 dest = getBits<21, 5>(instruction); // TODO: Safe multiplication equivalent (Multiplication is not IEEE compliant on the PICA) loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor); @@ -330,10 +329,10 @@ void ShaderEmitter::recDP4(const PICAShader& shader, u32 instruction) { void ShaderEmitter::recMAX(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; - u32 src1 = (instruction >> 12) & 0x7f; - const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment - const u32 idx = (instruction >> 19) & 3; - const u32 dest = (instruction >> 21) & 0x1f; + const u32 src1 = getBits<12, 7>(instruction); + const u32 src2 = getBits<7, 5>(instruction); // src2 coming first because PICA moment + const u32 idx = getBits<19, 2>(instruction); + const u32 dest = getBits<21, 5>(instruction); loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor); loadRegister<2>(src2_xmm, shader, src2, 0, operandDescriptor); @@ -343,10 +342,10 @@ void ShaderEmitter::recMAX(const PICAShader& shader, u32 instruction) { void ShaderEmitter::recMUL(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; - u32 src1 = (instruction >> 12) & 0x7f; - const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment - const u32 idx = (instruction >> 19) & 3; - const u32 dest = (instruction >> 21) & 0x1f; + const u32 src1 = getBits<12, 7>(instruction); + const u32 src2 = getBits<7, 5>(instruction); // src2 coming first because PICA moment + const u32 idx = getBits<19, 2>(instruction); + const u32 dest = getBits<21, 5>(instruction); // TODO: Safe multiplication equivalent (Multiplication is not IEEE compliant on the PICA) loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor); @@ -357,9 +356,9 @@ void ShaderEmitter::recMUL(const PICAShader& shader, u32 instruction) { void ShaderEmitter::recRCP(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; - u32 src = (instruction >> 12) & 0x7f; - const u32 idx = (instruction >> 19) & 3; - const u32 dest = (instruction >> 21) & 0x1f; + const u32 src = getBits<12, 7>(instruction); + const u32 idx = getBits<19, 2>(instruction); + const u32 dest = getBits<21, 5>(instruction); const u32 writeMask = operandDescriptor & 0xf; loadRegister<1>(src1_xmm, shader, src, idx, operandDescriptor); // Load source 1 into scratch1 @@ -376,9 +375,9 @@ void ShaderEmitter::recRCP(const PICAShader& shader, u32 instruction) { void ShaderEmitter::recRSQ(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; - u32 src = (instruction >> 12) & 0x7f; - const u32 idx = (instruction >> 19) & 3; - const u32 dest = (instruction >> 21) & 0x1f; + const u32 src = getBits<12, 7>(instruction); + const u32 idx = getBits<19, 2>(instruction); + const u32 dest = getBits<21, 5>(instruction); const u32 writeMask = operandDescriptor & 0xf; loadRegister<1>(src1_xmm, shader, src, idx, operandDescriptor); // Load source 1 into scratch1 @@ -395,11 +394,11 @@ void ShaderEmitter::recRSQ(const PICAShader& shader, u32 instruction) { void ShaderEmitter::recMAD(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x1f]; - const u32 src1 = (instruction >> 17) & 0x1f; - u32 src2 = (instruction >> 10) & 0x7f; - const u32 src3 = (instruction >> 5) & 0x1f; - const u32 idx = (instruction >> 22) & 3; - const u32 dest = (instruction >> 24) & 0x1f; + const u32 src1 = getBits<17, 5>(instruction); + const u32 src2 = getBits<10, 7>(instruction); + const u32 src3 = getBits<5, 5>(instruction); + const u32 idx = getBits<22, 2>(instruction); + const u32 dest = getBits<24, 5>(instruction); loadRegister<1>(src1_xmm, shader, src1, 0, operandDescriptor); loadRegister<2>(src2_xmm, shader, src2, idx, operandDescriptor); @@ -414,11 +413,11 @@ void ShaderEmitter::recMAD(const PICAShader& shader, u32 instruction) { void ShaderEmitter::recCMP(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; - const u32 src1 = (instruction >> 12) & 0x7f; - const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment - const u32 idx = (instruction >> 19) & 3; - const u32 cmpY = (instruction >> 21) & 7; - const u32 cmpX = (instruction >> 24) & 7; + const u32 src1 = getBits<12, 7>(instruction); + const u32 src2 = getBits<7, 5>(instruction); // src2 coming first because PICA moment + const u32 idx = getBits<19, 2>(instruction); + const u32 cmpY = getBits<21, 3>(instruction); + const u32 cmpX = getBits<24, 3>(instruction); loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor); loadRegister<2>(src2_xmm, shader, src2, 0, operandDescriptor); @@ -482,8 +481,8 @@ void ShaderEmitter::recCMP(const PICAShader& shader, u32 instruction) { void ShaderEmitter::recIFC(const PICAShader& shader, u32 instruction) { // z is 1 if true, else 0 checkCmpRegister(shader, instruction); - const u32 dest = (instruction >> 10) & 0xfff; const u32 num = instruction & 0xff; + const u32 dest = getBits<10, 12>(instruction); if (dest < recompilerPC) { Helpers::warn("Shader JIT: IFC instruction with dest < current PC\n"); @@ -507,8 +506,8 @@ void ShaderEmitter::recIFC(const PICAShader& shader, u32 instruction) { void ShaderEmitter::recIFU(const PICAShader& shader, u32 instruction) { // z is 0 if true, else 1 checkBoolUniform(shader, instruction); - const u32 dest = (instruction >> 10) & 0xfff; const u32 num = instruction & 0xff; + const u32 dest = getBits<10, 12>(instruction); if (dest < recompilerPC) { Helpers::warn("Shader JIT: IFC instruction with dest < current PC\n"); @@ -531,8 +530,8 @@ void ShaderEmitter::recIFU(const PICAShader& shader, u32 instruction) { } void ShaderEmitter::recCALL(const PICAShader& shader, u32 instruction) { - const u32 dest = (instruction >> 10) & 0xfff; const u32 num = instruction & 0xff; + const u32 dest = getBits<10, 12>(instruction); // Push return PC as stack parameter. This is a decently fast solution and Citra does the same but we should probably switch to a proper PICA-like // Callstack, because it's not great to have an infinitely expanding call stack where popping from empty stack is undefined as hell From 48e25870a59b1921ed601a51bb3121b33beca3f9 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 10 Jun 2023 16:09:13 +0300 Subject: [PATCH 022/103] Add const equvalent for OpenGL::Vector operator[] --- include/opengl.hpp | 4 +++- src/core/PICA/dynapica/shader_rec_emitter_x64.cpp | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/include/opengl.hpp b/include/opengl.hpp index 8cb1613e..32d1dfa6 100644 --- a/include/opengl.hpp +++ b/include/opengl.hpp @@ -20,6 +20,7 @@ #pragma once #include #include +#include #include #include #include @@ -546,7 +547,8 @@ namespace OpenGL { T& y() { return g(); } T& z() { return b(); } T& w() { return a(); } - T& operator[](int index) { return m_storage[index]; } + T& operator[](size_t index) { return m_storage[index]; } + const T& operator[](size_t index) const { return m_storage[index]; } T& u() { return r(); } T& v() { return g(); } diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index a2ddc9c1..5214613a 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -103,6 +103,7 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { case ShaderOpcodes::IFC: recIFC(shaderUnit, instruction); break; case ShaderOpcodes::IFU: recIFU(shaderUnit, instruction); break; case ShaderOpcodes::MOV: recMOV(shaderUnit, instruction); break; + case ShaderOpcodes::MOVA: recMOVA(shaderUnit, instruction); break; case ShaderOpcodes::MAX: recMAX(shaderUnit, instruction); break; case ShaderOpcodes::MUL: recMUL(shaderUnit, instruction); break; case ShaderOpcodes::NOP: break; @@ -280,6 +281,16 @@ void ShaderEmitter::recMOV(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; const u32 src = getBits<12, 7>(instruction); const u32 idx = getBits<19, 2>(instruction); + + loadRegister<1>(src1_xmm, shader, src, idx, operandDescriptor); // Load source 1 into scratch1 + u32 componentMask = operandDescriptor & 0xf; + Helpers::panic("Implement MOVA"); +} + +void ShaderEmitter::recMOVA(const PICAShader& shader, u32 instruction) { + const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; + const u32 src = getBits<12, 7>(instruction); + const u32 idx = getBits<19, 2>(instruction); const u32 dest = getBits<21, 5>(instruction); loadRegister<1>(src1_xmm, shader, src, idx, operandDescriptor); // Load source 1 into scratch1 From 5dcd828f666430f79b73945f16d4e2316ddc2a39 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 10 Jun 2023 17:23:29 +0300 Subject: [PATCH 023/103] [ShaderJIT] Implement mova --- .../PICA/dynapica/shader_rec_emitter_x64.cpp | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 5214613a..6d51bc87 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -175,7 +175,7 @@ void ShaderEmitter::loadRegister(Xmm dest, const PICAShader& shader, u32 src, u3 } default: - Helpers::panic("[ShaderJIT]: Unimplemented source index type"); + Helpers::panic("[ShaderJIT]: Unimplemented source index type %d", index); } if (negate) { @@ -281,20 +281,42 @@ void ShaderEmitter::recMOV(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; const u32 src = getBits<12, 7>(instruction); const u32 idx = getBits<19, 2>(instruction); + const u32 dest = getBits<21, 5>(instruction); loadRegister<1>(src1_xmm, shader, src, idx, operandDescriptor); // Load source 1 into scratch1 - u32 componentMask = operandDescriptor & 0xf; - Helpers::panic("Implement MOVA"); + storeRegister(src1_xmm, shader, dest, operandDescriptor); } void ShaderEmitter::recMOVA(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; const u32 src = getBits<12, 7>(instruction); const u32 idx = getBits<19, 2>(instruction); - const u32 dest = getBits<21, 5>(instruction); + const bool writeX = getBit<3>(operandDescriptor); // Should we write the x component of the address register? + const bool writeY = getBit<2>(operandDescriptor); + + static_assert(sizeof(shader.addrRegister) == 2 * sizeof(s32)); // Assert that the address register is 2 s32s + const uintptr_t addrRegisterOffset = uintptr_t(&shader.addrRegister[0]) - uintptr_t(&shader); + const uintptr_t addrRegisterYOffset = addrRegisterOffset + sizeof(shader.addrRegister[0]); + + // If no register is being written to then it is a nop. Probably not common but whatever + if (!writeX && !writeY) return; loadRegister<1>(src1_xmm, shader, src, idx, operandDescriptor); // Load source 1 into scratch1 - storeRegister(src1_xmm, shader, dest, operandDescriptor); + + // Write both + if (writeX && writeY) { + cvttps2dq(scratch1, src1_xmm); // Convert all lanes of src1 with truncation + movsd(qword[statePointer + addrRegisterOffset], scratch1); // Write back bottom 2 to addr register x and ys + } + else if (writeX) { + cvttss2si(scratch1, src1_xmm); // Convert bottom lane + movss(dword[statePointer + addrRegisterOffset], scratch1); // Write it back + } + else if (writeY) { + psrldq(src1_xmm, sizeof(float)); // Shift y component to bottom lane + cvttss2si(scratch1, src1_xmm); // Convert bottom lane + movss(dword[statePointer + addrRegisterYOffset], scratch1); // Write it back to y component + } } void ShaderEmitter::recADD(const PICAShader& shader, u32 instruction) { From 3c0ca97b5504d817fd09984c452e8e512ae4d1d6 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 10 Jun 2023 17:54:07 +0300 Subject: [PATCH 024/103] [Shader JIT] Add indexed register addressing --- .../PICA/dynapica/shader_rec_emitter_x64.cpp | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 6d51bc87..d84de99d 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -171,6 +171,23 @@ void ShaderEmitter::loadRegister(Xmm dest, const PICAShader& shader, u32 src, u3 movaps(dest, xword[statePointer + offset]); else // Swizzle is not trivial so we need to emit a shuffle instruction pshufd(dest, xword[statePointer + offset], convertedSwizzle); + + // Negate the register if necessary + if (negate) { + pxor(dest, xword[rip + negateVector]); + } + return; // Return. Rest of the function handles indexing which is not used if index == 0 + } + + case 1: { + const uintptr_t addrXOffset = uintptr_t(&shader.addrRegister[0]) - uintptr_t(&shader); + movsxd(rax, dword[statePointer + addrXOffset]); // rax = address register x + break; + } + + case 2: { + const uintptr_t addrYOffset = uintptr_t(&shader.addrRegister[1]) - uintptr_t(&shader); + movsxd(rax, dword[statePointer + addrYOffset]); // rax = address register y break; } @@ -178,6 +195,47 @@ void ShaderEmitter::loadRegister(Xmm dest, const PICAShader& shader, u32 src, u3 Helpers::panic("[ShaderJIT]: Unimplemented source index type %d", index); } + // Here we handle what happens when using indexed addressing & we can't predict what register will be read at compile time + // The index of the access is assumed to be in rax + // Add source register (src) and index (rax) to form the final register + add(rax, src); + + Label maybeTemp, maybeUniform, unknownReg, end; + const uintptr_t inputOffset = uintptr_t(&shader.inputs) - uintptr_t(&shader); + const uintptr_t tempOffset = uintptr_t(&shader.tempRegisters) - uintptr_t(&shader); + const uintptr_t uniformOffset = uintptr_t(&shader.floatUniforms) - uintptr_t(&shader); + + // If reg < 0x10, return inputRegisters[reg] + cmp(rax, 0x10); + jae(maybeTemp); + mov(rcx, rax); + shl(rcx, 4); // rcx = rax * sizeof(vec4 of floats) = rax * 16 + movaps(dest, xword[statePointer + rcx + inputOffset]); + jmp(end); + + // If (reg < 0x1F) return tempRegisters[reg - 0x10] + L(maybeTemp); + cmp(rax, 0x20); + jae(maybeUniform); + lea(rcx, qword[rax - 0x10]); + shl(rcx, 4); + movaps(dest, xword[statePointer + rcx + tempOffset]); + jmp(end); + + // If (reg < 0x80) return floatUniforms[reg - 0x20] + L(maybeUniform); + cmp(rax, 0x80); + jae(unknownReg); + lea(rcx, qword[rax - 0x20]); + shl(rcx, 4); + movaps(dest, xword[statePointer + rcx + uniformOffset]); + jmp(end); + + L(unknownReg); + pxor(dest, dest); // Set dest to 0 if we're reading from a garbage register + + L(end); + // Negate the register if necessary if (negate) { pxor(dest, xword[rip + negateVector]); } From 7e6f267462487d4d424bbbacc7c8ae7705d5d0a6 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 10 Jun 2023 18:20:15 +0300 Subject: [PATCH 025/103] [Shader JIT] Implement returning --- .../PICA/dynapica/shader_rec_emitter_x64.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index d84de99d..a6c8d583 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -82,7 +82,17 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { // See if PC is a possible return PC and emit the proper code if so if (std::binary_search(returnPCs.begin(), returnPCs.end(), recompilerPC)) { - int3(); + // This is the offset we need to add to rsp to peek the next return address in the callstack + // Ie the PICA PC address which, when reached, will trigger a return + const auto stackOffsetForPC = isWindows() ? (16 + 32) : 16; + + Label end; + // Check if return address == recompilerPC, ie if we should return + cmp(dword[rsp + stackOffsetForPC], recompilerPC); + jne(end); // If not, continue with uor lives + ret(); // If yes, return + + L(end); } // Fetch instruction and inc PC @@ -627,11 +637,14 @@ void ShaderEmitter::recCALL(const PICAShader& shader, u32 instruction) { // Push return PC as stack parameter. This is a decently fast solution and Citra does the same but we should probably switch to a proper PICA-like // Callstack, because it's not great to have an infinitely expanding call stack where popping from empty stack is undefined as hell push(qword, dest + num); - // Realign stack to 64 bits and allocate shadow space on windows + // Realign stack to 64 bits and allocate 32 bytes of shadow space on windows sub(rsp, isWindows() ? (8 + 32) : 8); // Call subroutine, Xbyak will update the label if it hasn't been initialized yet call(instructionLabels[dest]); + + // Fix up stack after returning. The 8 from before becomes a 16 because we're also skipping the qword we pushed + add(rsp, isWindows() ? (16 + 32) : 16); } #endif \ No newline at end of file From 158cbb8be1d2cbcab89d9922bab0b4ae6a61bd16 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 10 Jun 2023 21:50:05 +0300 Subject: [PATCH 026/103] [Shader JIT] Improve call/return --- .../PICA/dynapica/shader_rec_emitter_x64.cpp | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index a6c8d583..2e9b698f 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -35,10 +35,12 @@ void ShaderEmitter::compile(const PICAShader& shaderUnit) { // If we add integer register allocations they should be pushed here, and the rsp should be properly fixed up // However most of the PICA is floating point so yeah - // Allocate shadow stack on Windows - if constexpr (isWindows()) { - sub(rsp, 32); - } + // Push a return guard on the stack. This happens due to the way we handle the PICA callstack, by pushing the return PC to stack + // By pushing ffff'ffff, we make it impossible for a return check to erroneously pass + push(qword, 0xffffffff); + // Allocate 32 bytes of shadow stack on windows, and 8 more bytes to realign stack to 16 bytes after our 4-byte push + sub(rsp, isWindows() ? (8 + 32) : 8); + // Tail call to shader code entrypoint jmp(arg2); align(16); @@ -84,7 +86,7 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { if (std::binary_search(returnPCs.begin(), returnPCs.end(), recompilerPC)) { // This is the offset we need to add to rsp to peek the next return address in the callstack // Ie the PICA PC address which, when reached, will trigger a return - const auto stackOffsetForPC = isWindows() ? (16 + 32) : 16; + const auto stackOffsetForPC = isWindows() ? (8 + 32) : 8; Label end; // Check if return address == recompilerPC, ie if we should return @@ -335,10 +337,8 @@ void ShaderEmitter::checkBoolUniform(const PICAShader& shader, u32 instruction) void ShaderEmitter::recEND(const PICAShader& shader, u32 instruction) { // Undo anything the prologue did and return - // Dellocate shadow stack on Windows - if constexpr (isWindows()) { - add(rsp, 32); - } + // Dellocate shadow stack on Windows, and deallocate the stack space taken up for the return guard + add(rsp, isWindows() ? (16 + 32) : 16); // Restore registers pop(statePointer); @@ -550,8 +550,8 @@ void ShaderEmitter::recCMP(const PICAShader& shader, u32 instruction) { const u8 compareFuncX = conditionCodes[cmpX]; const u8 compareFuncY = conditionCodes[cmpY]; - static_assert(sizeof(bool) == 1 && sizeof(shader.cmpRegister) == 2); // The code below relies on bool being 1 byte exactly - const size_t cmpRegXOffset = uintptr_t(&shader.cmpRegister) - uintptr_t(&shader); + static_assert(sizeof(shader.cmpRegister[0]) == 1 && sizeof(shader.cmpRegister) == 2); // The code below relies on bool being 1 byte exactly + const size_t cmpRegXOffset = uintptr_t(&shader.cmpRegister[0]) - uintptr_t(&shader); const size_t cmpRegYOffset = cmpRegXOffset + sizeof(bool); // Cmp x and y are the same compare function, we can use a single cmp instruction @@ -597,7 +597,7 @@ void ShaderEmitter::recIFC(const PICAShader& shader, u32 instruction) { if (num == 0) { // Else block is empty, L(elseBlock); } else { // Else block is NOT empty - jmp(endIf, T_NEAR); + jmp(endIf, T_NEAR); // Skip executing the else branch if the if branch was ran L(elseBlock); compileUntil(shader, dest + num); L(endIf); @@ -621,9 +621,8 @@ void ShaderEmitter::recIFU(const PICAShader& shader, u32 instruction) { if (num == 0) { // Else block is empty, L(elseBlock); - } - else { // Else block is NOT empty - jmp(endIf, T_NEAR); + } else { // Else block is NOT empty + jmp(endIf, T_NEAR); // Skip executing the else branch if the if branch was ran L(elseBlock); compileUntil(shader, dest + num); L(endIf); @@ -637,14 +636,16 @@ void ShaderEmitter::recCALL(const PICAShader& shader, u32 instruction) { // Push return PC as stack parameter. This is a decently fast solution and Citra does the same but we should probably switch to a proper PICA-like // Callstack, because it's not great to have an infinitely expanding call stack where popping from empty stack is undefined as hell push(qword, dest + num); - // Realign stack to 64 bits and allocate 32 bytes of shadow space on windows - sub(rsp, isWindows() ? (8 + 32) : 8); + if constexpr (isWindows()) { + // Allocate 32 bytes of shadow space on windows + sub(rsp, 32); + } // Call subroutine, Xbyak will update the label if it hasn't been initialized yet call(instructionLabels[dest]); // Fix up stack after returning. The 8 from before becomes a 16 because we're also skipping the qword we pushed - add(rsp, isWindows() ? (16 + 32) : 16); + add(rsp, isWindows() ? (8 + 32) : 8); } #endif \ No newline at end of file From 4655ae08427862adb0e715f7333979400f7451bf Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 10 Jun 2023 22:08:15 +0300 Subject: [PATCH 027/103] [Shader JIT] Fix JIT-less build --- include/PICA/dynapica/shader_rec.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/PICA/dynapica/shader_rec.hpp b/include/PICA/dynapica/shader_rec.hpp index 711dabb0..b7d37b02 100644 --- a/include/PICA/dynapica/shader_rec.hpp +++ b/include/PICA/dynapica/shader_rec.hpp @@ -36,7 +36,11 @@ public: static constexpr bool isAvailable() { return true; } #else void prepare(PICAShader& shaderUnit) { - Helpers::panic("Vertex Loader JIT: Tried to load vertices with JIT on platform that does not support vertex loader jit"); + Helpers::panic("Vertex Loader JIT: Tried to run ShaderJIT::Prepare on platform that does not support shader jit"); + } + + void run(PICAShader& shaderUnit) { + Helpers::panic("Vertex Loader JIT: Tried to run ShaderJIT::Run on platform that does not support shader jit"); } // Define dummy callback. This should never be called if the shader JIT is not supported From b5dbc405878ae37dfb7379f6cc6fb64928c782e3 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 10 Jun 2023 23:18:33 +0300 Subject: [PATCH 028/103] [Shader JIT] Fix CMP instruction when cmpX == cmpY --- src/core/PICA/dynapica/shader_rec_emitter_x64.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 2e9b698f..4cd813c4 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -557,10 +557,11 @@ void ShaderEmitter::recCMP(const PICAShader& shader, u32 instruction) { // Cmp x and y are the same compare function, we can use a single cmp instruction if (cmpX == cmpY) { cmpps(lhs_x, rhs_x, compareFuncX); - movd(eax, lhs_x); - test(eax, eax); + movq(rax, lhs_x); // Move both comparison results to rax + test(eax, eax); // Check bottom 32 bits first + setne(byte[statePointer + cmpRegXOffset]); // set cmp.x - setne(byte[statePointer + cmpRegXOffset]); + shr(rax, 32); // Check top 32 bits (shr will set the zero flag properly) setne(byte[statePointer + cmpRegYOffset]); } else { movaps(scratch1, lhs_x); // Copy the left hand operands to temp registers From 402991a7ce51d31f19ccfb81ed143d813032e4e4 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 10 Jun 2023 23:33:28 +0300 Subject: [PATCH 029/103] Comment --- src/core/PICA/dynapica/shader_rec_emitter_x64.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 4cd813c4..d866332d 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -562,7 +562,7 @@ void ShaderEmitter::recCMP(const PICAShader& shader, u32 instruction) { setne(byte[statePointer + cmpRegXOffset]); // set cmp.x shr(rax, 32); // Check top 32 bits (shr will set the zero flag properly) - setne(byte[statePointer + cmpRegYOffset]); + setne(byte[statePointer + cmpRegYOffset]); // set cmp.y } else { movaps(scratch1, lhs_x); // Copy the left hand operands to temp registers movaps(scratch2, lhs_y); From 239cc3e495eede6a1876bc35fd52d08e52848eba Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 10 Jun 2023 23:37:26 +0300 Subject: [PATCH 030/103] Typo Co-Authored-By: liuk707 <62625900+liuk7071@users.noreply.github.com> --- src/core/loader/ncch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index c3dd8d2e..55c64ade 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -44,7 +44,7 @@ bool NCCH::loadFromHeader(u8* header, IOFile& file) { // It seems like some decryption tools will decrypt the file, without actually setting the NoCrypto flag in the NCCH header // This is a nice and easy hack to see if a file is pretending to be encrypted, taken from 3DMoo and Citra if (u32(programID) == u32(jumpID) && encrypted) { - printf("NCSD is supposedly ecrypted but not actually encrypted\n"); + printf("NCSD is supposedly encrypted but not actually encrypted\n"); encrypted = false; } else if (encrypted) { Helpers::panic("Encrypted NCSD file"); From 18df6f953197105970e52654dfb5a1762eb18fb1 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 10 Jun 2023 23:49:42 +0300 Subject: [PATCH 031/103] [ShaderJIT: MOVA] Fix oopsie --- src/core/PICA/dynapica/shader_rec_emitter_x64.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index d866332d..119e5ce3 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -377,13 +377,13 @@ void ShaderEmitter::recMOVA(const PICAShader& shader, u32 instruction) { movsd(qword[statePointer + addrRegisterOffset], scratch1); // Write back bottom 2 to addr register x and ys } else if (writeX) { - cvttss2si(scratch1, src1_xmm); // Convert bottom lane - movss(dword[statePointer + addrRegisterOffset], scratch1); // Write it back + cvttss2si(eax, src1_xmm); // Convert bottom lane + mov(dword[statePointer + addrRegisterOffset], eax); // Write it back } else if (writeY) { psrldq(src1_xmm, sizeof(float)); // Shift y component to bottom lane - cvttss2si(scratch1, src1_xmm); // Convert bottom lane - movss(dword[statePointer + addrRegisterYOffset], scratch1); // Write it back to y component + cvttss2si(eax, src1_xmm); // Convert bottom lane + mov(dword[statePointer + addrRegisterYOffset], eax); // Write it back to y component } } From 48b2af6a175b884a537e061f3100d3611ee0eafc Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sun, 11 Jun 2023 01:49:08 +0300 Subject: [PATCH 032/103] [Shader JIT] Mix in AVX-128 in some places, fix cmp --- .../PICA/dynapica/shader_rec_emitter_x64.hpp | 3 ++ .../PICA/dynapica/shader_rec_emitter_x64.cpp | 36 +++++++++++++------ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/include/PICA/dynapica/shader_rec_emitter_x64.hpp b/include/PICA/dynapica/shader_rec_emitter_x64.hpp index a253b9f9..ea391973 100644 --- a/include/PICA/dynapica/shader_rec_emitter_x64.hpp +++ b/include/PICA/dynapica/shader_rec_emitter_x64.hpp @@ -32,6 +32,7 @@ class ShaderEmitter : public Xbyak::CodeGenerator { u32 recompilerPC = 0; // PC the recompiler is currently recompiling @ bool haveSSE4_1 = false; // Shows if the CPU supports SSE4.1 + bool haveAVX = false; // Shows if the CPU supports AVX (NOT AVX2, NOT AVX512. Regular AVX) // Compile all instructions from [current recompiler PC, end) void compileUntil(const PICAShader& shaderUnit, u32 endPC); @@ -101,6 +102,8 @@ public: const auto cpu = Xbyak::util::Cpu(); haveSSE4_1 = cpu.has(Xbyak::util::Cpu::tSSE41); + haveAVX = cpu.has(Xbyak::util::Cpu::tAVX); + if (!cpu.has(Xbyak::util::Cpu::tSSE3)) { Helpers::panic("This CPU does not support SSE3. Please use the shader interpreter instead"); } diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 119e5ce3..8317662e 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -269,8 +269,12 @@ void ShaderEmitter::storeRegister(Xmm source, const PICAShader& shader, u32 dest if (index == 0) { // Bottom lane, no need to shift movss(dword[statePointer + lane_offset], source); } else { // Shift right by 32 * index, then write bottom lane - movaps(scratch1, source); - psrldq(scratch1, index * sizeof(float)); + if (haveAVX) { + vpsrldq(scratch1, source, index * sizeof(float)); + } else { + movaps(scratch1, source); + psrldq(scratch1, index * sizeof(float)); + } movss(dword[statePointer + lane_offset], scratch1); } } else if (haveSSE4_1) { @@ -505,9 +509,16 @@ void ShaderEmitter::recMAD(const PICAShader& shader, u32 instruction) { loadRegister<2>(src2_xmm, shader, src2, idx, operandDescriptor); loadRegister<3>(src3_xmm, shader, src3, 0, operandDescriptor); - movaps(scratch1, src1_xmm); // TODO: Implement safe PICA mul - mulps(scratch1, src2_xmm); + // Multiply src1 * src2 + if (haveAVX) { + vmulps(scratch1, src1_xmm, src2_xmm); + } else { + movaps(scratch1, src1_xmm); + mulps(scratch1, src2_xmm); + } + + // Add src3 addps(scratch1, src3_xmm); storeRegister(scratch1, shader, dest, operandDescriptor); } @@ -564,18 +575,23 @@ void ShaderEmitter::recCMP(const PICAShader& shader, u32 instruction) { shr(rax, 32); // Check top 32 bits (shr will set the zero flag properly) setne(byte[statePointer + cmpRegYOffset]); // set cmp.y } else { - movaps(scratch1, lhs_x); // Copy the left hand operands to temp registers - movaps(scratch2, lhs_y); + if (haveAVX) { + vcmpps(scratch1, lhs_x, rhs_x, compareFuncX); // Perform comparison for X component and store result in scratch1 + vcmpps(scratch2, lhs_y, rhs_y, compareFuncY); // Perform comparison for Y component and store result in scratch2 + } else { + movaps(scratch1, lhs_x); // Copy the left hand operands to temp registers + movaps(scratch2, lhs_y); - cmpps(scratch1, rhs_x, compareFuncX); // Perform the compares - cmpps(scratch2, rhs_y, compareFuncY); + cmpps(scratch1, rhs_x, compareFuncX); // Perform the compares + cmpps(scratch2, rhs_y, compareFuncY); + } movd(eax, scratch1); // Move results to eax for X and edx for Y - movd(edx, scratch2); + movq(rdx, scratch2); test(eax, eax); // Write back results with setne setne(byte[statePointer + cmpRegXOffset]); - test(edx, edx); + shr(rdx, 32); // We want the y component for the second comparison. This shift will set zero flag to 0 if the comparison is true setne(byte[statePointer + cmpRegYOffset]); } } From 71fd8de92fc7193a7a2fbde1a600822cdbcc3aee Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sun, 11 Jun 2023 22:08:02 +0300 Subject: [PATCH 033/103] [Shader JIT] Add MIN --- src/core/PICA/dynapica/shader_rec_emitter_x64.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 8317662e..9b9a3b04 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -117,6 +117,7 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { case ShaderOpcodes::MOV: recMOV(shaderUnit, instruction); break; case ShaderOpcodes::MOVA: recMOVA(shaderUnit, instruction); break; case ShaderOpcodes::MAX: recMAX(shaderUnit, instruction); break; + case ShaderOpcodes::MIN: recMIN(shaderUnit, instruction); break; case ShaderOpcodes::MUL: recMUL(shaderUnit, instruction); break; case ShaderOpcodes::NOP: break; case ShaderOpcodes::RCP: recRCP(shaderUnit, instruction); break; @@ -445,6 +446,19 @@ void ShaderEmitter::recMAX(const PICAShader& shader, u32 instruction) { storeRegister(src1_xmm, shader, dest, operandDescriptor); } +void ShaderEmitter::recMIN(const PICAShader& shader, u32 instruction) { + const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; + const u32 src1 = getBits<12, 7>(instruction); + const u32 src2 = getBits<7, 5>(instruction); // src2 coming first because PICA moment + const u32 idx = getBits<19, 2>(instruction); + const u32 dest = getBits<21, 5>(instruction); + + loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor); + loadRegister<2>(src2_xmm, shader, src2, 0, operandDescriptor); + minps(src1_xmm, src2_xmm); + storeRegister(src1_xmm, shader, dest, operandDescriptor); +} + void ShaderEmitter::recMUL(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; const u32 src1 = getBits<12, 7>(instruction); From 137b65284020876536266e6901373a0beed13ef8 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sun, 11 Jun 2023 23:43:47 +0300 Subject: [PATCH 034/103] [Shader JIT] Attempt to add CALLC/JMPC/JMPU/LOOP --- .../PICA/dynapica/shader_rec_emitter_x64.hpp | 8 ++ include/logger.hpp | 1 + .../PICA/dynapica/shader_rec_emitter_x64.cpp | 101 +++++++++++++++++- 3 files changed, 107 insertions(+), 3 deletions(-) diff --git a/include/PICA/dynapica/shader_rec_emitter_x64.hpp b/include/PICA/dynapica/shader_rec_emitter_x64.hpp index ea391973..636f88c2 100644 --- a/include/PICA/dynapica/shader_rec_emitter_x64.hpp +++ b/include/PICA/dynapica/shader_rec_emitter_x64.hpp @@ -3,6 +3,7 @@ // Only do anything if we're on an x64 target with JIT support enabled #if defined(PANDA3DS_DYNAPICA_SUPPORTED) && defined(PANDA3DS_X64_HOST) #include "helpers.hpp" +#include "logger.hpp" #include "PICA/shader.hpp" #include "xbyak/xbyak.h" #include "xbyak/xbyak_util.h" @@ -31,6 +32,8 @@ class ShaderEmitter : public Xbyak::CodeGenerator { Label negateVector; u32 recompilerPC = 0; // PC the recompiler is currently recompiling @ + u32 loopLevel = 0; // The current loop nesting level (0 = not in a loop) + bool haveSSE4_1 = false; // Shows if the CPU supports SSE4.1 bool haveAVX = false; // Shows if the CPU supports AVX (NOT AVX2, NOT AVX512. Regular AVX) @@ -39,6 +42,9 @@ class ShaderEmitter : public Xbyak::CodeGenerator { // Compile instruction "instr" void compileInstruction(const PICAShader& shaderUnit); + // Get the offset to be added to the rsp register to get the current return address + size_t getStackOffsetOfReturnPC(); + bool isCall(u32 instruction) { const u32 opcode = instruction >> 26; return (opcode == ShaderOpcodes::CALL) || (opcode == ShaderOpcodes::CALLC) || (opcode == ShaderOpcodes::CALLU); @@ -91,6 +97,8 @@ class ShaderEmitter : public Xbyak::CodeGenerator { void recSLT(const PICAShader& shader, u32 instruction); void recSLTI(const PICAShader& shader, u32 instruction); + MAKE_LOG_FUNCTION(log, shaderJITLogger) + public: using InstructionCallback = const void(*)(PICAShader& shaderUnit); // Callback type used for instructions // Callback type used for the JIT prologue. This is what the caller will call diff --git a/include/logger.hpp b/include/logger.hpp index 2ec69699..62129707 100644 --- a/include/logger.hpp +++ b/include/logger.hpp @@ -26,6 +26,7 @@ namespace Log { static Logger threadLogger; static Logger gpuLogger; static Logger rendererLogger; + static Logger shaderJITLogger; // Service loggers static Logger acLogger; diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 9b9a3b04..33fefb58 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -51,6 +51,7 @@ void ShaderEmitter::compile(const PICAShader& shaderUnit) { // Compile every instruction in the shader // This sounds horrible but the PICA instruction memory is tiny, and most of the time it's padded wtih nops that compile to nothing recompilerPC = 0; + loopLevel = 0; compileUntil(shaderUnit, PICAShader::maxInstructionCount); } @@ -78,15 +79,20 @@ void ShaderEmitter::compileUntil(const PICAShader& shaderUnit, u32 end) { } } +// This is the offset we need to add to rsp to peek the next return address in the callstack +// Ie the PICA PC address which, when reached, will trigger a return +size_t ShaderEmitter::getStackOffsetOfReturnPC() { + size_t ret = isWindows() ? (8 + 32) : 8; // Offset assuming 0 loop level + return ret; +} + void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { // Write current location to label for this instruction L(instructionLabels[recompilerPC]); // See if PC is a possible return PC and emit the proper code if so if (std::binary_search(returnPCs.begin(), returnPCs.end(), recompilerPC)) { - // This is the offset we need to add to rsp to peek the next return address in the callstack - // Ie the PICA PC address which, when reached, will trigger a return - const auto stackOffsetForPC = isWindows() ? (8 + 32) : 8; + const auto stackOffsetForPC = getStackOffsetOfReturnPC(); Label end; // Check if return address == recompilerPC, ie if we should return @@ -106,6 +112,9 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { case ShaderOpcodes::CALL: recCALL(shaderUnit, instruction); break; + case ShaderOpcodes::CALLC: + recCALLC(shaderUnit, instruction); + break; case ShaderOpcodes::CMP1: case ShaderOpcodes::CMP2: recCMP(shaderUnit, instruction); break; @@ -114,6 +123,9 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { case ShaderOpcodes::END: recEND(shaderUnit, instruction); break; case ShaderOpcodes::IFC: recIFC(shaderUnit, instruction); break; case ShaderOpcodes::IFU: recIFU(shaderUnit, instruction); break; + case ShaderOpcodes::JMPC: recJMPC(shaderUnit, instruction); break; + case ShaderOpcodes::JMPU: recJMPU(shaderUnit, instruction); break; + case ShaderOpcodes::LOOP: recLOOP(shaderUnit, instruction); break; case ShaderOpcodes::MOV: recMOV(shaderUnit, instruction); break; case ShaderOpcodes::MOVA: recMOVA(shaderUnit, instruction); break; case ShaderOpcodes::MAX: recMAX(shaderUnit, instruction); break; @@ -679,4 +691,87 @@ void ShaderEmitter::recCALL(const PICAShader& shader, u32 instruction) { add(rsp, isWindows() ? (8 + 32) : 8); } +void ShaderEmitter::recCALLC(const PICAShader& shader, u32 instruction) { + Label skipCall; + + // z is 1 if the call should be taken, 0 otherwise + checkCmpRegister(shader, instruction); + jnz(skipCall); + recCALL(shader, instruction); + + L(skipCall); +} + +void ShaderEmitter::recJMPC(const PICAShader& shader, u32 instruction) { + const u32 dest = getBits<10, 12>(instruction); + + Label& l = instructionLabels[dest]; + // Z is 1 if the comparison is true + checkCmpRegister(shader, instruction); + jz(l, T_NEAR); +} + +void ShaderEmitter::recJMPU(const PICAShader& shader, u32 instruction) { + bool jumpIfFalse = instruction & 1; // If the LSB is 0 we want to compare to true, otherwise compare to false + const u32 dest = getBits<10, 12>(instruction); + + Label& l = instructionLabels[dest]; + // Z is 0 if the uniform is true + checkBoolUniform(shader, instruction); + + if (jumpIfFalse) { + jz(l, T_NEAR); + } else { + jnz(l, T_NEAR); + } +} + +void ShaderEmitter::recLOOP(const PICAShader& shader, u32 instruction) { + const u32 dest = getBits<10, 12>(instruction); + const u32 uniformIndex = getBits<22, 2>(instruction); + + if (loopLevel > 0) { + log("[Shader JIT] Detected nested loop. Might be broken?\n"); + } + + if (dest < recompilerPC) { + Helpers::panic("[Shader JIT] Detected backwards loop\n"); + } + + loopLevel++; + + // Offset of the uniform + const uintptr_t uniformOffset = uintptr_t(&shader.intUniforms[uniformIndex]) - uintptr_t(&shader); + // Offset of the loop register + const uintptr_t loopRegOffset = uintptr_t(&shader.loopCounter) - uintptr_t(&shader); + + movzx(eax, byte[statePointer + uniformOffset]); // eax = loop iteration count + movzx(ecx, byte[statePointer + uniformOffset + sizeof(u8)]); // ecx = initial loop counter value + movzx(edx, byte[statePointer + uniformOffset + 2 * sizeof(u8)]); // edx = loop increment + + add(eax, 1); // The iteration count is actually uniform.x + 1 + mov(dword[statePointer + loopRegOffset], ecx); // Set loop counter + + // TODO: This might break if an instruction in a loop decides to yield... + push(rax); // Push loop iteration counter + push(rdx); // Push loop increment + if constexpr (isWindows()) + sub(rsp, 32); + + Label loopStart; + L(loopStart); + compileUntil(shader, dest + 1); + + const size_t stackOffsetOfLoopIncrement = isWindows() ? 32 : 0; + const size_t stackOffsetOfIterationCounter = stackOffsetOfLoopIncrement + 8; + + mov(ecx, dword[rsp + stackOffsetOfLoopIncrement]); // ecx = Loop increment + add(dword[statePointer + loopRegOffset], ecx); // Increment loop counter + sub(dword[rsp + stackOffsetOfIterationCounter], 1); // Subtract 1 from loop iteration counter + + jnz(loopStart); // Back to loop start if not over + add(rsp, isWindows() ? (16 + 32) : 16); + loopLevel--; +} + #endif \ No newline at end of file From 3f2b713c4b5359dc08d3b632d61cbdacf4898d06 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sun, 11 Jun 2023 23:51:45 +0300 Subject: [PATCH 035/103] [Shader JIT] Attempt to implement FLR and CALLU --- .../PICA/dynapica/shader_rec_emitter_x64.cpp | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 33fefb58..4fe59e36 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include using namespace Xbyak; using namespace Xbyak::util; @@ -115,12 +117,16 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { case ShaderOpcodes::CALLC: recCALLC(shaderUnit, instruction); break; + case ShaderOpcodes::CALLU: + recCALLU(shaderUnit, instruction); + break; case ShaderOpcodes::CMP1: case ShaderOpcodes::CMP2: recCMP(shaderUnit, instruction); break; case ShaderOpcodes::DP3: recDP3(shaderUnit, instruction); break; case ShaderOpcodes::DP4: recDP4(shaderUnit, instruction); break; case ShaderOpcodes::END: recEND(shaderUnit, instruction); break; + case ShaderOpcodes::FLR: recFLR(shaderUnit, instruction); break; case ShaderOpcodes::IFC: recIFC(shaderUnit, instruction); break; case ShaderOpcodes::IFU: recIFU(shaderUnit, instruction); break; case ShaderOpcodes::JMPC: recJMPC(shaderUnit, instruction); break; @@ -372,6 +378,23 @@ void ShaderEmitter::recMOV(const PICAShader& shader, u32 instruction) { storeRegister(src1_xmm, shader, dest, operandDescriptor); } +void ShaderEmitter::recFLR(const PICAShader& shader, u32 instruction) { + const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; + const u32 src = getBits<12, 7>(instruction); + const u32 idx = getBits<19, 2>(instruction); + const u32 dest = getBits<21, 5>(instruction); + + loadRegister<1>(src1_xmm, shader, src, idx, operandDescriptor); // Load source 1 into scratch1 + if (haveSSE4_1) { + roundps(src1_xmm, src1_xmm, _MM_FROUND_FLOOR); + } else { + cvttps2dq(src1_xmm, src1_xmm); // Truncate and convert to integer + cvtdq2ps(src1_xmm, src1_xmm); // Convert from integer back to float + } + + storeRegister(src1_xmm, shader, dest, operandDescriptor); +} + void ShaderEmitter::recMOVA(const PICAShader& shader, u32 instruction) { const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f]; const u32 src = getBits<12, 7>(instruction); @@ -702,6 +725,17 @@ void ShaderEmitter::recCALLC(const PICAShader& shader, u32 instruction) { L(skipCall); } +void ShaderEmitter::recCALLU(const PICAShader& shader, u32 instruction) { + Label skipCall; + + // z is 0 if the call should be taken, 1 otherwise + checkBoolUniform(shader, instruction); + jz(skipCall); + recCALL(shader, instruction); + + L(skipCall); +} + void ShaderEmitter::recJMPC(const PICAShader& shader, u32 instruction) { const u32 dest = getBits<10, 12>(instruction); From f538d38c98741f9108cfddfd729bd286c5df710e Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Mon, 12 Jun 2023 00:16:21 +0300 Subject: [PATCH 036/103] [Shader JIT] Add index = 3 to indexed register addressing --- src/core/PICA/dynapica/shader_rec_emitter_x64.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp index 4fe59e36..d334562d 100644 --- a/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp +++ b/src/core/PICA/dynapica/shader_rec_emitter_x64.cpp @@ -95,7 +95,7 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) { // See if PC is a possible return PC and emit the proper code if so if (std::binary_search(returnPCs.begin(), returnPCs.end(), recompilerPC)) { const auto stackOffsetForPC = getStackOffsetOfReturnPC(); - + Label end; // Check if return address == recompilerPC, ie if we should return cmp(dword[rsp + stackOffsetForPC], recompilerPC); @@ -221,6 +221,12 @@ void ShaderEmitter::loadRegister(Xmm dest, const PICAShader& shader, u32 src, u3 movsxd(rax, dword[statePointer + addrYOffset]); // rax = address register y break; } + + case 3: { + const uintptr_t loopCounterOffset = uintptr_t(&shader.loopCounter) - uintptr_t(&shader); + mov(eax, dword[statePointer + loopCounterOffset]); // rax = loop counter + break; + } default: Helpers::panic("[ShaderJIT]: Unimplemented source index type %d", index); From 8e90b8a103a39784be64512c7ff31eea93d77ada Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Tue, 13 Jun 2023 22:47:01 +0300 Subject: [PATCH 037/103] [GPU] Bottom screen stub --- include/memory.hpp | 3 + include/renderer_gl/renderer_gl.hpp | 3 + include/renderer_gl/surface_cache.hpp | 10 ++++ src/core/renderer_gl/renderer_gl.cpp | 83 ++++++++++++++++++++------- src/core/services/gsp_gpu.cpp | 26 ++++++++- src/emulator.cpp | 1 - 6 files changed, 101 insertions(+), 25 deletions(-) diff --git a/include/memory.hpp b/include/memory.hpp index 33b18ca5..ac71e7f3 100644 --- a/include/memory.hpp +++ b/include/memory.hpp @@ -33,7 +33,10 @@ namespace VirtualAddrs { NormalHeapStart = 0x08000000, LinearHeapStartOld = 0x14000000, // If kernel version < 0x22C + LinearHeapEndOld = 0x1C000000, + LinearHeapStartNew = 0x30000000, + LinearHeapEndNew = 0x40000000, // Start of TLS for first thread. Next thread's storage will be at TLSBase + 0x1000, and so on TLSBase = 0xFF400000, diff --git a/include/renderer_gl/renderer_gl.hpp b/include/renderer_gl/renderer_gl.hpp index ac11a813..2891db44 100644 --- a/include/renderer_gl/renderer_gl.hpp +++ b/include/renderer_gl/renderer_gl.hpp @@ -57,6 +57,9 @@ class Renderer { static constexpr u32 regNum = 0x300; // Number of internal PICA registers const std::array& regs; + OpenGL::Texture screenTexture; + OpenGL::Framebuffer screenFramebuffer; + OpenGL::Framebuffer getColourFBO(); OpenGL::Texture getTexture(Texture& tex); diff --git a/include/renderer_gl/surface_cache.hpp b/include/renderer_gl/surface_cache.hpp index 97a6d05e..e6b07763 100644 --- a/include/renderer_gl/surface_cache.hpp +++ b/include/renderer_gl/surface_cache.hpp @@ -13,6 +13,7 @@ // Are concerned. We could overload the == operator, but that implies full equality // Including equality of the allocated OpenGL resources, which we don't want // - A "valid" member that tells us whether the function is still valid or not +// - A "location" member which tells us which location in 3DS memory this surface occupies template class SurfaceCache { // Vanilla std::optional can't hold actual references @@ -40,6 +41,15 @@ public: return std::nullopt; } + OptionalRef findFromAddress(u32 address) { + for (auto& e : buffer) { + if (e.location == address && e.valid) + return e; + } + + return std::nullopt; + } + // Adds a surface object to the cache and returns it SurfaceType& add(const SurfaceType& surface) { if (size >= capacity) { diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 6da0dd70..81563563 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -187,8 +187,8 @@ void Renderer::initGraphicsContext() { OpenGL::Shader vertDisplay(displayVertexShader, OpenGL::Vertex); OpenGL::Shader fragDisplay(displayFragmentShader, OpenGL::Fragment); displayProgram.create({ vertDisplay, fragDisplay }); - displayProgram.use(); + displayProgram.use(); glUniform1i(OpenGL::uniformLocation(displayProgram, "u_texture"), 0); // Init sampler object vbo.createFixedSize(sizeof(Vertex) * vertexBufferSize, GL_STREAM_DRAW); @@ -208,18 +208,35 @@ void Renderer::initGraphicsContext() { dummyVBO.create(); dummyVAO.create(); + + // Create texture and framebuffer for the 3DS screen + const u32 screenTextureWidth = 2 * 400; // Top screen is 400 pixels wide, bottom is 320 + const u32 screenTextureHeight = 2 * 240; // Both screens are 240 pixels tall + + auto prevTexture = OpenGL::getTex2D(); + screenTexture.create(screenTextureWidth, screenTextureHeight, GL_RGBA8); + screenTexture.bind(); + screenTexture.setMinFilter(OpenGL::Linear); + screenTexture.setMagFilter(OpenGL::Linear); + glBindTexture(GL_TEXTURE_2D, prevTexture); + + screenFramebuffer.createWithDrawTexture(screenTexture); + screenFramebuffer.bind(OpenGL::DrawAndReadFramebuffer); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + Helpers::panic("Incomplete framebuffer"); + + // TODO: This should not clear the framebuffer contents. It should load them from VRAM. + GLint oldViewport[4]; + glGetIntegerv(GL_VIEWPORT, oldViewport); + OpenGL::setViewport(screenTextureWidth, screenTextureHeight); + OpenGL::setClearColor(0.0, 0.0, 0.0, 1.0); + OpenGL::clearColor(); + OpenGL::setViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]); + reset(); } -void Renderer::getGraphicsContext() { - OpenGL::disableScissor(); - OpenGL::setViewport(400, 240); - - vbo.bind(); - vao.bind(); - triangleProgram.use(); -} - // Set up the OpenGL blending context to match the emulated PICA void Renderer::setupBlending() { const bool blendingEnabled = (regs[PICAInternalRegs::ColourOperation] & (1 << 8)) != 0; @@ -266,6 +283,12 @@ void Renderer::setupBlending() { } void Renderer::drawVertices(OpenGL::Primitives primType, Vertex* vertices, u32 count) { + OpenGL::disableScissor(); + + vbo.bind(); + vao.bind(); + triangleProgram.use(); + // Adjust alpha test if necessary const u32 alphaControl = regs[PICAInternalRegs::AlphaTestConfig]; if (alphaControl != oldAlphaControl) { @@ -361,20 +384,11 @@ constexpr u32 bottomScreenBuffer = 0x1f05dc00; // Quick hack to display top screen for now void Renderer::display() { - OpenGL::disableBlend(); - OpenGL::disableDepth(); OpenGL::disableScissor(); - OpenGL::bindScreenFramebuffer(); - colourBufferCache[0].texture.bind(); - - displayProgram.use(); - - dummyVAO.bind(); - OpenGL::setClearColor(0.0, 0.0, 1.0, 1.0); // Clear screen colour - OpenGL::clearColor(); - OpenGL::setViewport(0, 240, 400, 240); // Actually draw our 3DS screen - OpenGL::draw(OpenGL::TriangleStrip, 4); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + screenFramebuffer.bind(OpenGL::ReadFramebuffer); + glBlitFramebuffer(0, 0, 400, 480, 0, 0, 400, 480, GL_COLOR_BUFFER_BIT, GL_LINEAR); } void Renderer::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) { @@ -408,6 +422,7 @@ OpenGL::Framebuffer Renderer::getColourFBO() { if (buffer.has_value()) { return buffer.value().get().fbo; } else { + printf("New colour buffer: %08X\n", colourBufferLoc); return colourBufferCache.add(sampleBuffer).fbo; } } @@ -450,4 +465,28 @@ void Renderer::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 const u32 outputWidth = outputSize & 0xffff; const u32 outputGap = outputSize >> 16; + + auto framebuffer = colourBufferCache.findFromAddress(inputAddr); + if (framebuffer.has_value()) { + OpenGL::Texture& texture = framebuffer.value().get().texture; + texture.bind(); + screenFramebuffer.bind(OpenGL::DrawFramebuffer); + + OpenGL::disableBlend(); + OpenGL::disableDepth(); + OpenGL::disableScissor(); + + displayProgram.use(); + + // Hack: Detect whether we are writing to the top or bottom screen by checking output gap and drawing to the proper part of the output texture + // We consider output gap == 320 to mean bottom, and anything else to mean top + if (outputGap == 320) { + OpenGL::setViewport(40, 0, 320, 240); + } else { + OpenGL::setViewport(0, 240, 400, 240); + } + + dummyVAO.bind(); + OpenGL::draw(OpenGL::TriangleStrip, 4); // Actually draw our 3DS screen + } } \ No newline at end of file diff --git a/src/core/services/gsp_gpu.cpp b/src/core/services/gsp_gpu.cpp index 76d2f4a5..9c0df876 100644 --- a/src/core/services/gsp_gpu.cpp +++ b/src/core/services/gsp_gpu.cpp @@ -331,9 +331,31 @@ void GPUService::memoryFill(u32* cmd) { } } +static u32 VaddrToPaddr(u32 addr) { + if (addr >= VirtualAddrs::VramStart && addr < (VirtualAddrs::VramStart + VirtualAddrs::VramSize)) [[likely]] { + return addr - VirtualAddrs::VramStart + PhysicalAddrs::VRAM; + } + + else if (addr >= VirtualAddrs::LinearHeapStartOld && addr < VirtualAddrs::LinearHeapEndOld) { + return addr - VirtualAddrs::LinearHeapStartOld + PhysicalAddrs::FCRAM; + } + + else if (addr >= VirtualAddrs::LinearHeapStartNew && addr < VirtualAddrs::LinearHeapEndNew) { + return addr - VirtualAddrs::LinearHeapStartNew + PhysicalAddrs::FCRAM; + } + + else if (addr == 0) { + return 0; + } + + Helpers::warn("[GSP::GPU VaddrToPaddr] Unknown virtual address %08X", addr); + // Obviously garbage address + return 0xF3310932; +} + void GPUService::triggerDisplayTransfer(u32* cmd) { - const u32 inputAddr = cmd[1]; - const u32 outputAddr = cmd[2]; + const u32 inputAddr = VaddrToPaddr(cmd[1]); + const u32 outputAddr = VaddrToPaddr(cmd[2]); const u32 inputSize = cmd[3]; const u32 outputSize = cmd[4]; const u32 flags = cmd[5]; diff --git a/src/emulator.cpp b/src/emulator.cpp index 8141a94a..52023fdd 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -23,7 +23,6 @@ void Emulator::render() { void Emulator::run() { while (running) { - gpu.getGraphicsContext(); // Give the GPU a rendering context runFrame(); // Run 1 frame of instructions gpu.display(); // Display graphics From 8284c3cda4c8ea3705bf9313aca23b6c9c56710b Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Tue, 13 Jun 2023 23:42:15 +0300 Subject: [PATCH 038/103] Remove printf --- src/core/renderer_gl/renderer_gl.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 81563563..aa581e5f 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -422,7 +422,6 @@ OpenGL::Framebuffer Renderer::getColourFBO() { if (buffer.has_value()) { return buffer.value().get().fbo; } else { - printf("New colour buffer: %08X\n", colourBufferLoc); return colourBufferCache.add(sampleBuffer).fbo; } } From f82b27ddba2ac2a5e9054cbb5e7bdcc2bbb34476 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Wed, 14 Jun 2023 14:46:11 +0300 Subject: [PATCH 039/103] Add xxhash --- CMakeLists.txt | 8 +- include/PICA/pica_hash.hpp | 0 src/core/PICA/pica_hash.cpp | 0 third_party/xxhash/.gitattributes | 10 + third_party/xxhash/.github/dependabot.yml | 7 + third_party/xxhash/.github/workflows/ci.yml | 589 + third_party/xxhash/.gitignore | 49 + third_party/xxhash/.tipi/deps | 1 + third_party/xxhash/.tipi/opts | 0 third_party/xxhash/.travis.yml | 142 + third_party/xxhash/CHANGELOG | 71 + third_party/xxhash/Doxyfile | 61 + third_party/xxhash/LICENSE | 26 + third_party/xxhash/Makefile | 623 + third_party/xxhash/README.md | 271 + third_party/xxhash/SECURITY.md | 13 + third_party/xxhash/appveyor.yml | 115 + third_party/xxhash/cli/.tipi/deps | 3 + third_party/xxhash/cli/.tipi/opts | 0 third_party/xxhash/cli/COPYING | 339 + third_party/xxhash/cli/README.md | 4 + third_party/xxhash/cli/xsum_arch.h | 159 + third_party/xxhash/cli/xsum_bench.c | 448 + third_party/xxhash/cli/xsum_bench.h | 51 + third_party/xxhash/cli/xsum_config.h | 214 + third_party/xxhash/cli/xsum_os_specific.c | 483 + third_party/xxhash/cli/xsum_os_specific.h | 89 + third_party/xxhash/cli/xsum_output.c | 66 + third_party/xxhash/cli/xsum_output.h | 62 + third_party/xxhash/cli/xsum_sanity_check.c | 694 + third_party/xxhash/cli/xsum_sanity_check.h | 60 + third_party/xxhash/cli/xxhsum.1 | 106 + third_party/xxhash/cli/xxhsum.1.md | 150 + third_party/xxhash/cli/xxhsum.c | 1305 + .../xxhash/cmake_unofficial/.gitignore | 12 + .../xxhash/cmake_unofficial/CMakeLists.txt | 204 + third_party/xxhash/cmake_unofficial/README.md | 37 + .../cmake_unofficial/xxHashConfig.cmake.in | 4 + third_party/xxhash/doc/README.md | 9 + third_party/xxhash/doc/xxhash.cry | 206 + third_party/xxhash/doc/xxhash_spec.md | 351 + third_party/xxhash/include/xxhash/xxh3.h | 55 + .../xxhash/include/xxhash/xxh_x86dispatch.h | 85 + third_party/xxhash/include/xxhash/xxhash.h | 6415 +++ third_party/xxhash/libxxhash.pc.in | 15 + third_party/xxhash/tests/Makefile | 127 + third_party/xxhash/tests/bench/.gitignore | 11 + third_party/xxhash/tests/bench/LICENSE | 339 + third_party/xxhash/tests/bench/Makefile | 68 + third_party/xxhash/tests/bench/benchHash.c | 166 + third_party/xxhash/tests/bench/benchHash.h | 67 + third_party/xxhash/tests/bench/benchfn.c | 253 + third_party/xxhash/tests/bench/benchfn.h | 183 + third_party/xxhash/tests/bench/bhDisplay.c | 160 + third_party/xxhash/tests/bench/bhDisplay.h | 61 + third_party/xxhash/tests/bench/hashes.h | 118 + third_party/xxhash/tests/bench/main.c | 220 + third_party/xxhash/tests/bench/timefn.c | 168 + third_party/xxhash/tests/bench/timefn.h | 89 + .../xxhash/tests/collisions/.gitignore | 2 + third_party/xxhash/tests/collisions/LICENSE | 339 + third_party/xxhash/tests/collisions/Makefile | 75 + third_party/xxhash/tests/collisions/README.md | 122 + .../tests/collisions/allcodecs/README.md | 1 + .../xxhash/tests/collisions/allcodecs/dummy.c | 38 + .../xxhash/tests/collisions/allcodecs/dummy.h | 45 + third_party/xxhash/tests/collisions/hashes.h | 127 + third_party/xxhash/tests/collisions/main.c | 1124 + third_party/xxhash/tests/collisions/pool.c | 344 + third_party/xxhash/tests/collisions/pool.h | 80 + third_party/xxhash/tests/collisions/sort.cc | 59 + third_party/xxhash/tests/collisions/sort.hh | 40 + .../xxhash/tests/collisions/threading.c | 82 + .../xxhash/tests/collisions/threading.h | 124 + third_party/xxhash/tests/filename-escape.sh | 21 + .../xxhash/tests/generate_unicode_test.c | 154 + third_party/xxhash/tests/multiInclude.c | 78 + third_party/xxhash/tests/ppc_define.c | 62 + third_party/xxhash/tests/sanity_test.c | 764 + .../xxhash/tests/sanity_test_vectors.h | 45832 ++++++++++++++++ .../tests/sanity_test_vectors_generator.c | 464 + third_party/xxhash/tests/unicode_lint.sh | 43 + third_party/xxhash/xxh_x86dispatch.c | 802 + third_party/xxhash/xxhash.c | 43 + 84 files changed, 66504 insertions(+), 3 deletions(-) create mode 100644 include/PICA/pica_hash.hpp create mode 100644 src/core/PICA/pica_hash.cpp create mode 100644 third_party/xxhash/.gitattributes create mode 100644 third_party/xxhash/.github/dependabot.yml create mode 100644 third_party/xxhash/.github/workflows/ci.yml create mode 100644 third_party/xxhash/.gitignore create mode 100644 third_party/xxhash/.tipi/deps create mode 100644 third_party/xxhash/.tipi/opts create mode 100644 third_party/xxhash/.travis.yml create mode 100644 third_party/xxhash/CHANGELOG create mode 100644 third_party/xxhash/Doxyfile create mode 100644 third_party/xxhash/LICENSE create mode 100644 third_party/xxhash/Makefile create mode 100644 third_party/xxhash/README.md create mode 100644 third_party/xxhash/SECURITY.md create mode 100644 third_party/xxhash/appveyor.yml create mode 100644 third_party/xxhash/cli/.tipi/deps create mode 100644 third_party/xxhash/cli/.tipi/opts create mode 100644 third_party/xxhash/cli/COPYING create mode 100644 third_party/xxhash/cli/README.md create mode 100644 third_party/xxhash/cli/xsum_arch.h create mode 100644 third_party/xxhash/cli/xsum_bench.c create mode 100644 third_party/xxhash/cli/xsum_bench.h create mode 100644 third_party/xxhash/cli/xsum_config.h create mode 100644 third_party/xxhash/cli/xsum_os_specific.c create mode 100644 third_party/xxhash/cli/xsum_os_specific.h create mode 100644 third_party/xxhash/cli/xsum_output.c create mode 100644 third_party/xxhash/cli/xsum_output.h create mode 100644 third_party/xxhash/cli/xsum_sanity_check.c create mode 100644 third_party/xxhash/cli/xsum_sanity_check.h create mode 100644 third_party/xxhash/cli/xxhsum.1 create mode 100644 third_party/xxhash/cli/xxhsum.1.md create mode 100644 third_party/xxhash/cli/xxhsum.c create mode 100644 third_party/xxhash/cmake_unofficial/.gitignore create mode 100644 third_party/xxhash/cmake_unofficial/CMakeLists.txt create mode 100644 third_party/xxhash/cmake_unofficial/README.md create mode 100644 third_party/xxhash/cmake_unofficial/xxHashConfig.cmake.in create mode 100644 third_party/xxhash/doc/README.md create mode 100644 third_party/xxhash/doc/xxhash.cry create mode 100644 third_party/xxhash/doc/xxhash_spec.md create mode 100644 third_party/xxhash/include/xxhash/xxh3.h create mode 100644 third_party/xxhash/include/xxhash/xxh_x86dispatch.h create mode 100644 third_party/xxhash/include/xxhash/xxhash.h create mode 100644 third_party/xxhash/libxxhash.pc.in create mode 100644 third_party/xxhash/tests/Makefile create mode 100644 third_party/xxhash/tests/bench/.gitignore create mode 100644 third_party/xxhash/tests/bench/LICENSE create mode 100644 third_party/xxhash/tests/bench/Makefile create mode 100644 third_party/xxhash/tests/bench/benchHash.c create mode 100644 third_party/xxhash/tests/bench/benchHash.h create mode 100644 third_party/xxhash/tests/bench/benchfn.c create mode 100644 third_party/xxhash/tests/bench/benchfn.h create mode 100644 third_party/xxhash/tests/bench/bhDisplay.c create mode 100644 third_party/xxhash/tests/bench/bhDisplay.h create mode 100644 third_party/xxhash/tests/bench/hashes.h create mode 100644 third_party/xxhash/tests/bench/main.c create mode 100644 third_party/xxhash/tests/bench/timefn.c create mode 100644 third_party/xxhash/tests/bench/timefn.h create mode 100644 third_party/xxhash/tests/collisions/.gitignore create mode 100644 third_party/xxhash/tests/collisions/LICENSE create mode 100644 third_party/xxhash/tests/collisions/Makefile create mode 100644 third_party/xxhash/tests/collisions/README.md create mode 100644 third_party/xxhash/tests/collisions/allcodecs/README.md create mode 100644 third_party/xxhash/tests/collisions/allcodecs/dummy.c create mode 100644 third_party/xxhash/tests/collisions/allcodecs/dummy.h create mode 100644 third_party/xxhash/tests/collisions/hashes.h create mode 100644 third_party/xxhash/tests/collisions/main.c create mode 100644 third_party/xxhash/tests/collisions/pool.c create mode 100644 third_party/xxhash/tests/collisions/pool.h create mode 100644 third_party/xxhash/tests/collisions/sort.cc create mode 100644 third_party/xxhash/tests/collisions/sort.hh create mode 100644 third_party/xxhash/tests/collisions/threading.c create mode 100644 third_party/xxhash/tests/collisions/threading.h create mode 100644 third_party/xxhash/tests/filename-escape.sh create mode 100644 third_party/xxhash/tests/generate_unicode_test.c create mode 100644 third_party/xxhash/tests/multiInclude.c create mode 100644 third_party/xxhash/tests/ppc_define.c create mode 100644 third_party/xxhash/tests/sanity_test.c create mode 100644 third_party/xxhash/tests/sanity_test_vectors.h create mode 100644 third_party/xxhash/tests/sanity_test_vectors_generator.c create mode 100644 third_party/xxhash/tests/unicode_lint.sh create mode 100644 third_party/xxhash/xxh_x86dispatch.c create mode 100644 third_party/xxhash/xxhash.c diff --git a/CMakeLists.txt b/CMakeLists.txt index ed3d298c..2fd3cabc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,8 @@ include_directories(third_party/imgui/) include_directories(third_party/dynarmic/src) include_directories(third_party/cryptopp/) include_directories(third_party/cityhash/include) -include_directories(third_party/result/include/) +include_directories(third_party/result/include) +include_directories(third_party/xxhash/include) add_compile_definitions(NOMINMAX) add_compile_definitions(SDL_MAIN_HANDLED) @@ -94,7 +95,7 @@ set(SERVICE_SOURCE_FILES src/core/services/service_manager.cpp src/core/services ) set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp src/core/PICA/regs.cpp src/core/PICA/shader_unit.cpp src/core/PICA/shader_interpreter.cpp src/core/PICA/dynapica/shader_rec.cpp - src/core/PICA/dynapica/shader_rec_emitter_x64.cpp + src/core/PICA/dynapica/shader_rec_emitter_x64.cpp src/core/PICA/pica_hash.cpp ) set(RENDERER_GL_SOURCE_FILES src/core/renderer_gl/renderer_gl.cpp src/core/renderer_gl/textures.cpp src/core/renderer_gl/etc1.cpp) @@ -124,7 +125,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp inc include/services/ldr_ro.hpp include/ipc.hpp include/services/act.hpp include/services/nfc.hpp include/system_models.hpp include/services/dlp_srvr.hpp include/PICA/dynapica/pica_recs.hpp include/PICA/dynapica/x64_regs.hpp include/PICA/dynapica/vertex_loader_rec.hpp include/PICA/dynapica/shader_rec.hpp - include/PICA/dynapica/shader_rec_emitter_x64.hpp + include/PICA/dynapica/shader_rec_emitter_x64.hpp include/PICA/pica_hash.hpp ) set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp @@ -135,6 +136,7 @@ set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp third_party/gl3w/gl3w.cpp third_party/cityhash/cityhash.cpp + third_party/xxhash/xxhash.c ) source_group("Header Files\\Core" FILES ${HEADER_FILES}) diff --git a/include/PICA/pica_hash.hpp b/include/PICA/pica_hash.hpp new file mode 100644 index 00000000..e69de29b diff --git a/src/core/PICA/pica_hash.cpp b/src/core/PICA/pica_hash.cpp new file mode 100644 index 00000000..e69de29b diff --git a/third_party/xxhash/.gitattributes b/third_party/xxhash/.gitattributes new file mode 100644 index 00000000..fbcf75b5 --- /dev/null +++ b/third_party/xxhash/.gitattributes @@ -0,0 +1,10 @@ +# Set the default behavior +* text eol=lf + +# Explicitly declare source files +*.c text eol=lf +*.h text eol=lf + +# Denote files that should not be modified. +*.odt binary + diff --git a/third_party/xxhash/.github/dependabot.yml b/third_party/xxhash/.github/dependabot.yml new file mode 100644 index 00000000..7bb4cf76 --- /dev/null +++ b/third_party/xxhash/.github/dependabot.yml @@ -0,0 +1,7 @@ +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/third_party/xxhash/.github/workflows/ci.yml b/third_party/xxhash/.github/workflows/ci.yml new file mode 100644 index 00000000..9a21b5a6 --- /dev/null +++ b/third_party/xxhash/.github/workflows/ci.yml @@ -0,0 +1,589 @@ +# Known critical issues: +# - AVX512 related tests are incomplete. Because default environment of +# GitHub Actions doesn't guarantee to support AVX512. +# As of May 2021, they're using Xeon E5-2673 (which doesn't support +# AVX512) and Xeon Platinum 8171M (which supports AVX512). +# See also https://github.com/actions/runner/issues/1069 +# +# In this CI script, it always run `make default` which compiles xxHash +# with AVX512 intrinsics. But if test runner doesn't support AVX512, +# it doesn't run `make check` which tests runtime error/consistency. +# It means that this test stochastically detects a failure in AVX512 +# code path. +# +# Known issues: +# - This test script ignores exit code of cppcheck which can see under +# Job:Linux x64 misc tests > cppcheck in the GitHub Actions report. +# Because xxHash project doesn't 100% follow their recommendation. +# Also sometimes it reports false positives. +# +# - GitHub Actions doesn't support Visual Studio 2015 and 2013. +# https://github.com/actions/virtual-environments/issues/387 +# +# - Setup procedure for msys2 environment is painfully slow. It takes +# 3..5 minutes. +# +# Notes: +# - You can investigate various information at the right pane of GitHub +# Actions report page. +# +# | Item | Section in the right pane | +# | ------------------------- | ------------------------------------- | +# | OS, VM | Set up job | +# | git repo, commit hash | Run actions/checkout@v3 | +# | gcc, tools | Environment info | +# +# - To fail earlier, order of tests in the same job are roughly sorted by +# elapsed time. +# +# Todos: +# - [ ] Linux: Add native ARM runner. +# - [ ] Linux: Add native ARM64 runner. +# - [ ] Linux: Add native PPC64LE runner. +# - [ ] Linux: Add native S390X runner. +# - [ ] Windows: Add VS2013. +# - [ ] Windows: Add VS2015. +# - [ ] Windows: Add clang for msys2. +# - [ ] Windows: Add native or emulated ARM runner. +# - [ ] Windows: Add native or emulated ARM64 runner. + + +# Name of the workflow is also displayed as a SVG badge +name: xxHash CI tests + +on: [push, pull_request] + +concurrency: + group: fast-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + xxhash-c-compilers: + name: CC=${{ matrix.cc }}, ${{ matrix.os }} + strategy: + fail-fast: false # 'false' means Don't stop matrix workflows even if some matrix entry fails. + matrix: + include: [ + # You can access the following values via ${{ matrix.??? }} + # + # pkgs : apt-get package names. It can include multiple package names which are delimited by space. + # cc : C compiler executable. + # cxx : C++ compiler executable for `make ctocpptest`. + # avx512 : Set 'true' if compiler supports avx512. Otherwise, set 'false'. + # os : GitHub Actions YAML workflow label. See https://github.com/actions/virtual-environments#available-environments + + # cc + { pkgs: '', cc: cc, cxx: c++, avx512: 'true', os: ubuntu-latest, }, + + # gcc + { pkgs: '', cc: gcc, cxx: g++, avx512: 'true', os: ubuntu-latest, }, + { pkgs: 'gcc-12 g++-12 lib32gcc-12-dev', cc: gcc-12, cxx: g++-12, avx512: 'true', os: ubuntu-22.04, }, + { pkgs: 'gcc-11 g++-11 lib32gcc-11-dev', cc: gcc-11, cxx: g++-11, avx512: 'true', os: ubuntu-22.04, }, + { pkgs: 'gcc-10 g++-10 lib32gcc-10-dev', cc: gcc-10, cxx: g++-10, avx512: 'true', os: ubuntu-22.04, }, + { pkgs: 'gcc-9 g++-9 lib32gcc-9-dev', cc: gcc-9, cxx: g++-9, avx512: 'true', os: ubuntu-22.04, }, + { pkgs: 'gcc-8 g++-8 lib32gcc-8-dev', cc: gcc-8, cxx: g++-8, avx512: 'true', os: ubuntu-20.04, }, + { pkgs: 'gcc-7 g++-7 lib32gcc-7-dev', cc: gcc-7, cxx: g++-7, avx512: 'true', os: ubuntu-20.04, }, + + # clang + { pkgs: '', cc: clang, cxx: clang++, avx512: 'true', os: ubuntu-latest, }, + { pkgs: 'clang-12', cc: clang-12, cxx: clang++-12, avx512: 'true', os: ubuntu-22.04, }, + { pkgs: 'clang-11', cc: clang-11, cxx: clang++-11, avx512: 'true', os: ubuntu-22.04, }, + { pkgs: 'clang-10', cc: clang-10, cxx: clang++-10, avx512: 'true', os: ubuntu-20.04, }, + { pkgs: 'clang-9', cc: clang-9, cxx: clang++-9, avx512: 'true', os: ubuntu-20.04, }, + { pkgs: 'clang-8', cc: clang-8, cxx: clang++-8, avx512: 'true', os: ubuntu-20.04, }, + { pkgs: 'clang-7', cc: clang-7, cxx: clang++-7, avx512: 'true', os: ubuntu-20.04, }, + { pkgs: 'clang-6.0', cc: clang-6.0, cxx: clang++-6.0, avx512: 'true', os: ubuntu-20.04, }, + ] + + runs-on: ${{ matrix.os }} + env: # Set environment variables + # We globally set CC and CXX to improve compatibility with .travis.yml + CC: ${{ matrix.cc }} + CXX: ${{ matrix.cxx }} + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + + - name: apt-get install + run: | + sudo apt-get update + sudo apt-get install gcc-multilib + sudo apt-get install ${{ matrix.pkgs }} + + - name: Environment info + run: | + echo && type $CC && which $CC && $CC --version + echo && type $CXX && which $CXX && $CXX --version + echo && type make && make -v + echo && cat /proc/cpuinfo || echo /proc/cpuinfo is not present + + - name: C90 + no-long-long compliance + if: always() + run: | + CFLAGS="-std=c90 -pedantic -Wno-long-long -Werror" make clean xxhsum + + - name: C90 + XXH_NO_LONG_LONG + if: always() + run: | + # strict c90, with no long long support; resulting in no XXH64_* symbol + make clean c90test + + - name: dispatch + if: always() + run: | + # removing sign conversion warnings due to a bug in gcc-5's definition of some AVX512 intrinsics + CFLAGS="-Werror" MOREFLAGS="-Wno-sign-conversion" make clean dispatch + + - name: DISPATCH=1 + if: always() + run: | + CFLAGS="-Wall -Wextra -Werror" make DISPATCH=1 clean default + + - name: XXH_SIZE_OPT == 2 + if: always() + run: | + CFLAGS="-Os -DXXH_SIZE_OPT=2 -Wall -Wextra -Werror" make clean xxhsum + + - name: noxxh3test + if: always() + run: | + # check library can be compiled with XXH_NO_XXH3, resulting in no XXH3_* symbol + make clean noxxh3test + + - name: nostreamtest + if: always() + run: | + # check library can be compiled with XXH_NO_STREAM, resulting in no streaming symbols + make clean noxxh3test + + - name: make avx512f + if: ${{ matrix.avx512 == 'true' }} + run: | + CFLAGS="-O1 -mavx512f -Werror" make clean default + + - name: test-all + if: always() + run: | + make clean test-all + + + ubuntu-consistency: + name: Linux x64 check results consistency + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + + - name: Environment info + run: | + echo && gcc --version + echo && make -v + echo && cat /proc/cpuinfo || echo /proc/cpuinfo is not present + + - name: Scalar code path + run: | + CPPFLAGS=-DXXH_VECTOR=XXH_SCALAR make clean check + + - name: SSE2 code path + run: | + CPPFLAGS=-DXXH_VECTOR=XXH_SSE2 make clean check + + - name: AVX2 code path + run: | + CPPFLAGS="-mavx2 -DXXH_VECTOR=XXH_AVX2" make clean check + + # As for AVX512, see "Known critical issues" at the top of this file + - name: AVX512 code path + run: | + # Run "make check" if /proc/cpuinfo has flags for avx512. + grep -q "^flags.*\bavx512\b" /proc/cpuinfo && CPPFLAGS="-mavx512f -DXXH_VECTOR=XXH_AVX512" make clean check || (echo This test runner does not support AVX512. && $(exit 0)) + + - name: reroll code path (#240) + run: | + CPPFLAGS=-DXXH_REROLL=1 make clean check + + - name: tests/bench + run: | + make -C tests/bench + + + ubuntu-misc: + name: Linux x64 misc tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + + - name: apt-get install + run: | + sudo apt-get update + sudo apt-get install valgrind cppcheck + + - name: Environment info + run: | + echo && gcc --version + echo && clang --version + echo && valgrind --version + echo && cppcheck --version + echo && make -v + echo && cat /proc/cpuinfo || echo /proc/cpuinfo is not present + + - name: cppcheck + run: | + # This test script ignores exit code of cppcheck. See knowin issues + # at the top of this file. + make clean cppcheck || echo There are some cppcheck reports + + - name: test-mem (valgrind) + run: | + make clean test-mem + + - name: usan + run: | + make clean usan + + - name: Lint Unicode in root-dir, cli/, tests/, tests/bench/, tests/collisions/. + run: | + make lint-unicode + + - name: test-filename-escape + # See also issue #695 - https://github.com/Cyan4973/xxHash/issues/695 + run: | + make clean test-filename-escape + + ubuntu-cmake-unofficial: + name: Linux x64 cmake unofficial build test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + + - name: Environment info + run: | + echo && gcc --version + echo && cmake --version + echo && make -v + echo && cat /proc/cpuinfo || echo /proc/cpuinfo is not present + + - name: cmake + run: | + cd cmake_unofficial + mkdir build + cd build + cmake .. + CFLAGS=-Werror make + mkdir test_install_dir + DESTDIR=test_install_dir cmake --install . + + - name: cmake minimum version v2.8.12 test + run: | + mkdir -p cmake_bins + cd cmake_bins + wget https://cmake.org/files/v2.8/cmake-2.8.12.2-Linux-i386.tar.gz + tar xzf cmake-2.8.12.2-Linux-i386.tar.gz + cd ../cmake_unofficial + mkdir -p build + cd build + ../../cmake_bins/cmake-2.8.12.2-Linux-i386/bin/cmake --version + ../../cmake_bins/cmake-2.8.12.2-Linux-i386/bin/cmake .. + ../../cmake_bins/cmake-2.8.12.2-Linux-i386/bin/cmake --build . + mkdir -p test_install_dir + DESTDIR=test_install_dir ../../cmake_bins/cmake-2.8.12.2-Linux-i386/bin/cmake --install . + rm -rf * + ../../cmake_bins/cmake-2.8.12.2-Linux-i386/bin/cmake -DCMAKE_BUILD_TYPE=Debug .. + ../../cmake_bins/cmake-2.8.12.2-Linux-i386/bin/cmake --build . + + + + # Linux, { ARM, ARM64, PPC64LE, PPC64, S390X } + # All tests are using QEMU and gcc cross compiler. + + qemu-consistency: + name: QEMU ${{ matrix.name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false # 'false' means Don't stop matrix workflows even if some matrix failed. + matrix: + include: [ + { name: 'ARM', xcc_pkg: gcc-arm-linux-gnueabi, xcc: arm-linux-gnueabi-gcc, xemu_pkg: qemu-system-arm, xemu: qemu-arm-static, os: ubuntu-latest, }, + { name: 'AARCH64', xcc_pkg: gcc-aarch64-linux-gnu, xcc: aarch64-linux-gnu-gcc, xemu_pkg: qemu-system-arm, xemu: qemu-aarch64-static, os: ubuntu-latest, }, + { name: 'PPC64LE', xcc_pkg: gcc-powerpc64le-linux-gnu, xcc: powerpc64le-linux-gnu-gcc, xemu_pkg: qemu-system-ppc, xemu: qemu-ppc64le-static, os: ubuntu-latest, }, + { name: 'PPC64', xcc_pkg: gcc-powerpc64-linux-gnu, xcc: powerpc64-linux-gnu-gcc, xemu_pkg: qemu-system-ppc, xemu: qemu-ppc64-static, os: ubuntu-latest, }, + { name: 'S390X', xcc_pkg: gcc-s390x-linux-gnu, xcc: s390x-linux-gnu-gcc, xemu_pkg: qemu-system-s390x, xemu: qemu-s390x-static, os: ubuntu-latest, }, + { name: 'MIPS', xcc_pkg: gcc-mips-linux-gnu, xcc: mips-linux-gnu-gcc, xemu_pkg: qemu-system-mips, xemu: qemu-mips-static, os: ubuntu-latest, }, + { name: 'M68K', xcc_pkg: gcc-m68k-linux-gnu, xcc: m68k-linux-gnu-gcc, xemu_pkg: qemu-system-m68k, xemu: qemu-m68k-static, os: ubuntu-latest, }, + + { name: 'ARM, gcc-10', xcc_pkg: gcc-10-arm-linux-gnueabi, xcc: arm-linux-gnueabi-gcc-10, xemu_pkg: qemu-system-arm, xemu: qemu-arm-static, os: ubuntu-20.04, }, + { name: 'AARCH64, gcc-10', xcc_pkg: gcc-10-aarch64-linux-gnu, xcc: aarch64-linux-gnu-gcc-10, xemu_pkg: qemu-system-arm, xemu: qemu-aarch64-static, os: ubuntu-20.04, }, + { name: 'PPC64LE, gcc-10', xcc_pkg: gcc-10-powerpc64le-linux-gnu, xcc: powerpc64le-linux-gnu-gcc-10, xemu_pkg: qemu-system-ppc, xemu: qemu-ppc64le-static, os: ubuntu-20.04, }, + { name: 'PPC64, gcc-10', xcc_pkg: gcc-10-powerpc64-linux-gnu, xcc: powerpc64-linux-gnu-gcc-10, xemu_pkg: qemu-system-ppc, xemu: qemu-ppc64-static, os: ubuntu-20.04, }, + { name: 'S390X, gcc-10', xcc_pkg: gcc-10-s390x-linux-gnu, xcc: s390x-linux-gnu-gcc-10, xemu_pkg: qemu-system-s390x, xemu: qemu-s390x-static, os: ubuntu-20.04, }, + { name: 'MIPS, gcc-10', xcc_pkg: gcc-10-mips-linux-gnu, xcc: mips-linux-gnu-gcc-10, xemu_pkg: qemu-system-mips, xemu: qemu-mips-static, os: ubuntu-20.04, }, + + { name: 'ARM, gcc-9', xcc_pkg: gcc-9-arm-linux-gnueabi, xcc: arm-linux-gnueabi-gcc-9, xemu_pkg: qemu-system-arm, xemu: qemu-arm-static, os: ubuntu-20.04, }, + { name: 'AARCH64, gcc-9', xcc_pkg: gcc-9-aarch64-linux-gnu, xcc: aarch64-linux-gnu-gcc-9, xemu_pkg: qemu-system-arm, xemu: qemu-aarch64-static, os: ubuntu-20.04, }, + { name: 'PPC64LE, gcc-9', xcc_pkg: gcc-9-powerpc64le-linux-gnu, xcc: powerpc64le-linux-gnu-gcc-9, xemu_pkg: qemu-system-ppc, xemu: qemu-ppc64le-static, os: ubuntu-20.04, }, + { name: 'PPC64, gcc-9', xcc_pkg: gcc-9-powerpc64-linux-gnu, xcc: powerpc64-linux-gnu-gcc-9, xemu_pkg: qemu-system-ppc, xemu: qemu-ppc64-static, os: ubuntu-20.04, }, + { name: 'S390X, gcc-9', xcc_pkg: gcc-9-s390x-linux-gnu, xcc: s390x-linux-gnu-gcc-9, xemu_pkg: qemu-system-s390x, xemu: qemu-s390x-static, os: ubuntu-20.04, }, + { name: 'MIPS, gcc-9', xcc_pkg: gcc-9-mips-linux-gnu, xcc: mips-linux-gnu-gcc-9, xemu_pkg: qemu-system-mips, xemu: qemu-mips-static, os: ubuntu-20.04, }, + + { name: 'ARM, gcc-8', xcc_pkg: gcc-8-arm-linux-gnueabi, xcc: arm-linux-gnueabi-gcc-8, xemu_pkg: qemu-system-arm, xemu: qemu-arm-static, os: ubuntu-20.04, }, + # aarch64-linux-gnu-gcc-8 linker has an issue for LDFLAGS="-static" + # { name: 'AARCH64, gcc-8', xcc_pkg: gcc-8-aarch64-linux-gnu, xcc: aarch64-linux-gnu-gcc-8, xemu_pkg: qemu-system-arm, xemu: qemu-aarch64-static, os: ubuntu-20.04, }, + { name: 'PPC64LE, gcc-8', xcc_pkg: gcc-8-powerpc64le-linux-gnu, xcc: powerpc64le-linux-gnu-gcc-8, xemu_pkg: qemu-system-ppc, xemu: qemu-ppc64le-static, os: ubuntu-20.04, }, + { name: 'PPC64, gcc-8', xcc_pkg: gcc-8-powerpc64-linux-gnu, xcc: powerpc64-linux-gnu-gcc-8, xemu_pkg: qemu-system-ppc, xemu: qemu-ppc64-static, os: ubuntu-20.04, }, + { name: 'S390X, gcc-8', xcc_pkg: gcc-8-s390x-linux-gnu, xcc: s390x-linux-gnu-gcc-8, xemu_pkg: qemu-system-s390x, xemu: qemu-s390x-static, os: ubuntu-20.04, }, + # ubuntu-20.04 fails to retrieve gcc-8-mips-linux-gnu for some reason. + # { name: 'MIPS, gcc-8', xcc_pkg: gcc-8-mips-linux-gnu, xcc: mips-linux-gnu-gcc-8, xemu_pkg: qemu-system-mips, xemu: qemu-mips-static, os: ubuntu-20.04, }, + ] + env: # Set environment variables + XCC: ${{ matrix.xcc }} + XEMU: ${{ matrix.xemu }} + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - name: apt update & install (1) + run: | + sudo apt-get update + sudo apt-get install gcc-multilib g++-multilib qemu-utils qemu-user-static + + - name: Environment info (1) + run: | + echo && apt-cache search "^gcc-" | grep "linux" | sort + + - name: apt update & install (2) + run: | + sudo apt-get install ${{ matrix.xcc_pkg }} ${{ matrix.xemu_pkg }} + + - name: Environment info (2) + run: | + echo && which $XCC + echo && $XCC --version + echo && $XCC -v # Show built-in specs + echo && which $XEMU + echo && $XEMU --version + + - name: ARM (XXH_VECTOR=[ scalar, NEON ]) + if: ${{ startsWith(matrix.name, 'ARM') }} + run: | + CPPFLAGS="-DXXH_VECTOR=XXH_SCALAR" LDFLAGS="-static" CC=$XCC RUN_ENV=$XEMU make clean check + CPPFLAGS="-DXXH_VECTOR=XXH_NEON" CFLAGS="-O3 -march=armv7-a -fPIC -mfloat-abi=softfp -mfpu=neon-vfpv4" LDFLAGS="-static" CC=$XCC RUN_ENV=$XEMU make clean check + + - name: AARCH64 (XXH_VECTOR=[ scalar, NEON, SVE ]) + if: ${{ startsWith(matrix.name, 'AARCH64') }} + run: | + CPPFLAGS="-DXXH_VECTOR=XXH_SCALAR" LDFLAGS="-static" CC=$XCC RUN_ENV=$XEMU make clean check + CPPFLAGS="-DXXH_VECTOR=XXH_NEON" LDFLAGS="-static" CC=$XCC RUN_ENV=$XEMU make clean check + CPPFLAGS="-DXXH_VECTOR=XXH_SVE" LDFLAGS="-static" CC=$XCC RUN_ENV="$XEMU -cpu max,sve128=on,sve256=off,sve512=off,sve1024=off,sve2048=off" make clean check + CPPFLAGS="-DXXH_VECTOR=XXH_SVE" LDFLAGS="-static" CC=$XCC RUN_ENV="$XEMU -cpu max,sve128=on,sve256=on,sve512=off,sve1024=off,sve2048=off" make clean check + CPPFLAGS="-DXXH_VECTOR=XXH_SVE" LDFLAGS="-static" CC=$XCC RUN_ENV="$XEMU -cpu max,sve128=on,sve256=on,sve512=on,sve1024=off,sve2048=off" make clean check + CPPFLAGS="-DXXH_VECTOR=XXH_SVE" LDFLAGS="-static" CC=$XCC RUN_ENV="$XEMU -cpu max,sve128=on,sve256=on,sve512=on,sve1024=on,sve2048=off" make clean check + CPPFLAGS="-DXXH_VECTOR=XXH_SVE" LDFLAGS="-static" CC=$XCC RUN_ENV="$XEMU -cpu max,sve128=on,sve256=on,sve512=on,sve1024=on,sve2048=on" make clean check + + - name: PPC64(LE) (XXH_VECTOR=[ scalar, VSX ]) + if: ${{ startsWith(matrix.name, 'PPC64') }} + run: | + CPPFLAGS="-DXXH_VECTOR=XXH_SCALAR" LDFLAGS="-static" CC=$XCC RUN_ENV=$XEMU make clean check + CPPFLAGS="-DXXH_VECTOR=XXH_VSX" CFLAGS="-O3 -maltivec -mvsx -mpower8-vector -mcpu=power8" LDFLAGS="-static" CC=$XCC RUN_ENV=$XEMU make clean check + + - name: S390X (XXH_VECTOR=[ scalar, VSX ]) + if: ${{ startsWith(matrix.name, 'S390X') }} + run: | + CPPFLAGS="-DXXH_VECTOR=XXH_SCALAR" LDFLAGS="-static" CC=$XCC RUN_ENV=$XEMU make clean check + CPPFLAGS=-DXXH_VECTOR=XXH_VSX CFLAGS="-O3 -march=arch11 -mzvector" LDFLAGS="-static" CC=$XCC RUN_ENV=$XEMU make clean check + + - name: MIPS (XXH_VECTOR=[ scalar ]) + if: ${{ startsWith(matrix.name, 'MIPS') }} + run: | + LDFLAGS="-static" CC=$XCC RUN_ENV=$XEMU make clean check + + - name: M68K (XXH_VECTOR=[ scalar ]) + if: ${{ startsWith(matrix.name, 'M68K') }} + run: | + LDFLAGS="-static" CC=$XCC RUN_ENV=$XEMU make clean check + + + # macOS, { 11 } + + macos-general: + name: ${{ matrix.system.os }} + runs-on: ${{ matrix.system.os }} + strategy: + fail-fast: false # 'false' means Don't stop matrix workflows even if some matrix failed. + matrix: + system: [ + { os: macos-11 }, + { os: macos-12 }, + ] + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + + - name: Environment info + run: | + echo && clang --version + echo && sysctl -a | grep machdep.cpu # cpuinfo + + - name: make + run: | + CFLAGS="-Werror" make clean default + + - name: make test + run: | + # test scenario where "stdout" is not the console + make clean test MOREFLAGS='-Werror' | tee + + + # Windows, { VC++2022, VC++2019, VC++2017 } x { x64, Win32, ARM, ARM64 } + # + # - Default shell for Windows environment is PowerShell Core. + # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell + # + # - "windows-2022" uses Visual Studio 2022. + # https://github.com/actions/virtual-environments/blob/main/images/win/Windows2022-Readme.md#visual-studio-enterprise-2022 + # + # - "windows-2019" uses Visual Studio 2019. + # https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md#visual-studio-enterprise-2019 + + windows-visualc-general: + name: ${{ matrix.system.vc }}, ${{ matrix.arch }} + runs-on: ${{ matrix.system.os }} # Runs-on foreach value of strategy.matrix.system.os + strategy: + fail-fast: false # 'false' means Don't stop matrix workflows even if some matrix failed. + matrix: + system: [ + { os: windows-2022, vc: "VC++ 2022", clangcl: 'false', }, # CMake failed to configure clang-cl with VC++2022. + { os: windows-2019, vc: "VC++ 2019", clangcl: 'true', }, + ] + arch: [ x64, Win32, ARM, ARM64 ] + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + + - name: Build ${{ matrix.system.os }}, ${{ matrix.arch }} + run: | + cd cmake_unofficial + mkdir build + cd build + cmake .. -DCMAKE_BUILD_TYPE=Release -A ${{ matrix.arch }} -DXXHASH_C_FLAGS="/WX" + cmake --build . --config Release + + - name: Test + # Run benchmark for testing only if target arch is x64 or Win32. + if: ${{ matrix.arch == 'x64' || matrix.arch == 'Win32' }} + run: | + .\cmake_unofficial\build\Release\xxhsum.exe -bi1 + + - name: Build ${{ matrix.system.os }}, clang-cl, ${{ matrix.arch }} + if: ${{ matrix.system.clangcl == 'true' }} + run: | + cd cmake_unofficial + mkdir build-clang-cl + cd build-clang-cl + cmake .. -DCMAKE_BUILD_TYPE=Release -A x64 -DCMAKE_GENERATOR_TOOLSET=ClangCL + cmake --build . --config Release + + - name: Test (clang-cl) + # Run benchmark for testing only if target arch is x64 or Win32. + if: ${{ matrix.system.clangcl == 'true' && ( matrix.arch == 'x64' || matrix.arch == 'Win32' ) }} + run: | + .\cmake_unofficial\build-clang-cl\Release\xxhsum.exe -bi1 + + + # Windows, { mingw64, mingw32 } + # + # - Shell for msys2 is sh (msys2). defaults.run.shell is for this setting. + # + # https://github.com/msys2/MINGW-packages/blob/master/.github/workflows/main.yml + # https://github.com/actions/starter-workflows/issues/95 + + windows-msys2-general: + name: Windows ${{ matrix.msystem }} + runs-on: windows-latest + strategy: + fail-fast: false # 'false' means Don't stop matrix workflows even if some matrix failed. + matrix: + include: [ + { msystem: mingw64, toolchain: mingw-w64-x86_64-toolchain }, + { msystem: mingw32, toolchain: mingw-w64-i686-toolchain }, + ] + defaults: + run: + shell: msys2 {0} + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: msys2/setup-msys2@7efe20baefed56359985e327d329042cde2434ff # v2 + with: + msystem: MSYS + install: mingw-w64-i686-make ${{ matrix.toolchain }} + update: true + + - name: Update + run: | + pacman --noconfirm -Suuy + pacman --noconfirm -Suu + + - name: mingw64 + if: ${{ matrix.msystem == 'mingw64' }} + run: | + PATH=/mingw64/bin:$PATH /mingw32/bin/mingw32-make clean test MOREFLAGS=-Werror + PATH=/mingw64/bin:$PATH /mingw32/bin/mingw32-make -C tests/bench + # Abort if result of "file ./xxhsum.exe" doesn't contain 'x86-64'. + # Expected output is "./xxhsum.exe: PE32+ executable (console) x86-64, for MS Windows" + file ./xxhsum.exe | grep -q 'x86-64' || $(exit 1) + ./xxhsum.exe --version + + - name: mingw32 + if: ${{ matrix.msystem == 'mingw32' }} + run: | + PATH=/mingw32/bin:$PATH /mingw32/bin/mingw32-make.exe clean test MOREFLAGS=-Werror + PATH=/mingw32/bin:$PATH /mingw32/bin/mingw32-make.exe -C tests/bench + # Abort if result of "file ./xxhsum.exe" doesn't contain '80386'. + # Expected output is "./xxhsum.exe: PE32 executable (console) Intel 80386, for MS Windows" + file ./xxhsum.exe | grep -q '80386' || $(exit 1) + ./xxhsum.exe --version + + tipi-build-linux: + name: tipi.build project build and dependency resolution + runs-on: ubuntu-latest + container: tipibuild/tipi-ubuntu + + env: + HOME: /root + + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + # FIX: we currently need a full clone + with: + fetch-depth: '0' + - run: | + mkdir -p /usr/local/share/.tipi + # FIX: Hack for github action + git config --global --add safe.directory /usr/local/share/.tipi + git config --global --add safe.directory /__w/xxHash/xxHash/ + + # checking if the xxHash project builds and passes tests + - name: Build as project target linux-cxx17 (run test multiInclude) + run: tipi . --dont-upgrade --verbose --test multiInclude -t linux-cxx17 + + - name: Build as project target linux-cxx20 (run test multiInclude) + run: tipi . --dont-upgrade --verbose --test multiInclude -t linux-cxx20 + + - name: Cleanup project builds + run: rm -r ./build + + # trying if pulling the dependency with tipi works properly + # + # note: the xxhashsum sources include xxhash using a #include "../xxhash.h" + # this defeats the purpose of this test AND because a bug post tipi v0.0.35 + # because of source mirroring (not) supporting relative include to locations + # outside of the project tree. + # + # because of this we create a copy of the ./cli and apply some sed magic + # to make the includes proper 'library' includes to simulate what someone + # consuming xxHash would do + # + # e.g. turning #include "../xxhash.h" => #include + - name: Build as dependency + run: | + cp -a ./cli ./cli-tipi + cd ./cli-tipi + find ./ -type f -iname "*.c" | xargs sed -i 's;"../xxhash.h";;g' + tipi . --dont-upgrade --verbose -t linux-cxx17 + ./build/linux-cxx17/bin/xsum_os_specific diff --git a/third_party/xxhash/.gitignore b/third_party/xxhash/.gitignore new file mode 100644 index 00000000..797f2d2a --- /dev/null +++ b/third_party/xxhash/.gitignore @@ -0,0 +1,49 @@ +# objects +*.o +*.obj +*.s + +# libraries +libxxhash.* +!libxxhash.pc.in + +# Executables +*.exe +xxh32sum +xxh64sum +xxh128sum +xxhsum +xxhsum32 +xxhsum_privateXXH +xxhsum_inlinedXXH +dispatch +tests/generate_unicode_test +tests/sanity_test +tests/sanity_test_vectors_generator + +# local conf +.clang_complete + +# Mac OS-X artefacts +*.dSYM +.DS_Store + +# Wasm / emcc / emscripten artefacts +*.html +*.wasm +*.js + +# CMake build directories +build*/ + +# project managers artifacts +.projectile + +# analyzer artifacts +infer-out + +# test artifacts +.test* +tmp* +tests/*.unicode +tests/unicode_test* diff --git a/third_party/xxhash/.tipi/deps b/third_party/xxhash/.tipi/deps new file mode 100644 index 00000000..6f31cf5a --- /dev/null +++ b/third_party/xxhash/.tipi/deps @@ -0,0 +1 @@ +{ } \ No newline at end of file diff --git a/third_party/xxhash/.tipi/opts b/third_party/xxhash/.tipi/opts new file mode 100644 index 00000000..e69de29b diff --git a/third_party/xxhash/.travis.yml b/third_party/xxhash/.travis.yml new file mode 100644 index 00000000..9f9e42ca --- /dev/null +++ b/third_party/xxhash/.travis.yml @@ -0,0 +1,142 @@ +language: c + +# Dump CPU info before start +before_install: + - cat /proc/cpuinfo || echo /proc/cpuinfo is not present + +matrix: + fast_finish: true + include: + + - name: General linux x64 tests + arch: amd64 + addons: + apt: + packages: + - g++-multilib + - gcc-multilib + - cppcheck + script: + - make -B test-all + - make clean + - CFLAGS="-Werror" MOREFLAGS="-Wno-sign-conversion" make dispatch # removing sign conversion warnings due to a bug in gcc-5's definition of some AVX512 intrinsics + - make clean + - CFLAGS="-O1 -mavx512f -Werror" make + - make clean + - CFLAGS="-Wall -Wextra -Werror" make DISPATCH=1 + - make clean + - CFLAGS="-std=c90 -pedantic -Wno-long-long -Werror" make xxhsum # check C90 + long long compliance + - make c90test # strict c90, with no long long support; resulting in no XXH64_* symbol + - make noxxh3test # check library can be compiled with XXH_NO_XXH3, resulting in no XXH3_* symbol + + + - name: Check results consistency on x64 + arch: amd64 + script: + - CPPFLAGS=-DXXH_VECTOR=XXH_SCALAR make check # Scalar code path + - make clean + - CPPFLAGS=-DXXH_VECTOR=XXH_SSE2 make check # SSE2 code path + - make clean + - CPPFLAGS="-mavx2 -DXXH_VECTOR=XXH_AVX2" make check # AVX2 code path + - make clean + - CPPFLAGS="-mavx512f -DXXH_VECTOR=XXH_AVX512" make check # AVX512 code path + - make clean + - CPPFLAGS=-DXXH_REROLL=1 make check # reroll code path (#240) + - make -C tests/bench + + - name: macOS General Test + os: osx + compiler: clang + script: + - CFLAGS="-Werror" make # test library build + - make clean + - make test MOREFLAGS='-Werror' | tee # test scenario where `stdout` is not the console + + - name: ARM compilation and consistency checks (Qemu) + dist: xenial + arch: amd64 + addons: + apt: + packages: + - qemu-system-arm + - qemu-user-static + - gcc-arm-linux-gnueabi + - libc6-dev-armel-cross + script: + # arm (32-bit) + - CC=arm-linux-gnueabi-gcc CPPFLAGS=-DXXH_VECTOR=XXH_SCALAR LDFLAGS=-static RUN_ENV=qemu-arm-static make check # Scalar code path + - make clean + # NEON (32-bit) + - CC=arm-linux-gnueabi-gcc CPPFLAGS=-DXXH_VECTOR=XXH_NEON CFLAGS="-O3 -march=armv7-a -fPIC -mfloat-abi=softfp -mfpu=neon-vfpv4" LDFLAGS=-static RUN_ENV=qemu-arm-static make check # NEON code path + + - name: aarch64 compilation and consistency checks + dist: xenial + arch: arm64 + script: + # aarch64 + - CPPFLAGS=-DXXH_VECTOR=XXH_SCALAR make check # Scalar code path + # NEON (64-bit) + - make clean + - CPPFLAGS=-DXXH_VECTOR=XXH_NEON make check # NEON code path + # clang + - make clean + - CC=clang CPPFLAGS=-DXXH_VECTOR=XXH_SCALAR make check # Scalar code path + # clang + NEON + - make clean + - CC=clang CPPFLAGS=-DXXH_VECTOR=XXH_NEON make check # NEON code path + + # We need Bionic here because the QEMU versions shipped in the older repos + # do not support POWER8 emulation, and compiling QEMU from source is a pain. + - name: PowerPC + PPC64 compilation and consistency checks (Qemu on Bionic) + dist: bionic + arch: amd64 + addons: + apt: + packages: + - qemu-system-ppc + - qemu-user-static + - gcc-powerpc-linux-gnu + - gcc-powerpc64-linux-gnu + - libc6-dev-powerpc-cross + - libc6-dev-ppc64-cross + script: + - CC=powerpc-linux-gnu-gcc RUN_ENV=qemu-ppc-static LDFLAGS=-static make check # Scalar code path + - make clean + - CC=powerpc64-linux-gnu-gcc RUN_ENV=qemu-ppc64-static CPPFLAGS=-DXXH_VECTOR=XXH_SCALAR CFLAGS="-O3" LDFLAGS="-static -m64" make check # Scalar code path + # VSX code + - make clean + - CC=powerpc64-linux-gnu-gcc RUN_ENV="qemu-ppc64-static -cpu power8" CPPFLAGS=-DXXH_VECTOR=XXH_VSX CFLAGS="-O3 -maltivec -mvsx -mcpu=power8 -mpower8-vector" LDFLAGS="-static -m64" make check # VSX code path + # altivec.h redefinition issue #426 + - make clean + - CC=powerpc64-linux-gnu-gcc CPPFLAGS=-DXXH_VECTOR=XXH_VSX CFLAGS="-maltivec -mvsx -mcpu=power8 -mpower8-vector" make -C tests test_ppc_redefine + + - name: PPC64LE compilation and consistency checks + dist: xenial + arch: ppc64le + script: + # Scalar (universal) code path + - CPPFLAGS=-DXXH_VECTOR=XXH_SCALAR LDFLAGS=-static make check + # VSX code path (64-bit) + - make clean + - CPPFLAGS=-DXXH_VECTOR=XXH_VSX CFLAGS="-O3 -maltivec -mvsx -mpower8-vector -mcpu=power8" LDFLAGS="-static" make check + # altivec.h redefinition issue #426 + - make clean + - CPPFLAGS=-DXXH_VECTOR=XXH_VSX CFLAGS="-maltivec -mvsx -mcpu=power8 -mpower8-vector" make -C tests test_ppc_redefine + + - name: IBM s390x compilation and consistency checks + dist: bionic + arch: s390x + script: + # Scalar (universal) code path + - CPPFLAGS=-DXXH_VECTOR=XXH_SCALAR LDFLAGS=-static make check + # s390x code path (64-bit) + - make clean + - CPPFLAGS=-DXXH_VECTOR=XXH_VSX CFLAGS="-O3 -march=arch11 -mzvector" LDFLAGS="-static" make check + + - name: cmake build test + script: + - cd cmake_unofficial + - mkdir build + - cd build + - cmake .. + - CFLAGS=-Werror make diff --git a/third_party/xxhash/CHANGELOG b/third_party/xxhash/CHANGELOG new file mode 100644 index 00000000..ff59d8bb --- /dev/null +++ b/third_party/xxhash/CHANGELOG @@ -0,0 +1,71 @@ +v0.8.1 +- perf : much improved performance for XXH3 streaming variants, notably on gcc and msvc +- perf : improved XXH64 speed and latency on small inputs +- perf : small XXH32 speed and latency improvement on small inputs of random size +- perf : minor stack usage improvement for XXH32 and XXH64 +- api : new experimental variants XXH3_*_withSecretandSeed() +- api : update XXH3_generateSecret(), can no generate secret of any size (>= XXH3_SECRET_SIZE_MIN) +- cli : xxhsum can now generate and check XXH3 checksums, using command `-H3` +- build: can build xxhash without XXH3, with new build macro XXH_NO_XXH3 +- build: fix xxh_x86dispatch build with MSVC, by @apankrat +- build: XXH_INLINE_ALL can always be used safely, even after XXH_NAMESPACE or a previous XXH_INLINE_ALL +- build: improved PPC64LE vector support, by @mpe +- install: fix pkgconfig, by @ellert +- install: compatibility with Haiku, by @Begasus +- doc : code comments made compatible with doxygen, by @easyaspi314 +- misc : XXH_ACCEPT_NULL_INPUT_POINTER is no longer necessary, all functions can accept NULL input pointers, as long as size == 0 +- misc : complete refactor of CI tests on Github Actions, offering much larger coverage, by @t-mat +- misc : xxhsum code base split into multiple specialized units, within directory cli/, by @easyaspi314 + +v0.8.0 +- api : stabilize XXH3 +- cli : xxhsum can parse BSD-style --check lines, by @WayneD +- cli : `xxhsum -` accepts console input, requested by @jaki +- cli : xxhsum accepts -- separator, by @jaki +- cli : fix : print correct default algo for symlinked helpers, by @martinetd +- install: improved pkgconfig script, allowing custom install locations, requested by @ellert + +v0.7.4 +- perf: automatic vector detection and selection at runtime (`xxh_x86dispatch.h`), initiated by @easyaspi314 +- perf: added AVX512 support, by @gzm55 +- api : new: secret generator `XXH_generateSecret()`, suggested by @koraa +- api : fix: XXH3_state_t is movable, identified by @koraa +- api : fix: state is correctly aligned in AVX mode (unlike `malloc()`), by @easyaspi314 +- api : fix: streaming generated wrong values in some combination of random ingestion lengths, reported by @WayneD +- cli : fix unicode print on Windows, by @easyaspi314 +- cli : can `-c` check file generated by sfv +- build: `make DISPATCH=1` generates `xxhsum` and `libxxhash` with runtime vector detection (x86/x64 only) +- install: cygwin installation support +- doc : Cryptol specification of XXH32 and XXH64, by @weaversa + +v0.7.3 +- perf: improved speed for large inputs (~+20%) +- perf: improved latency for small inputs (~10%) +- perf: s390x Vectorial code, by @easyaspi314 +- cli: improved support for Unicode filenames on Windows, thanks to @easyaspi314 and @t-mat +- api: `xxhash.h` can now be included in any order, with and without `XXH_STATIC_LINKING_ONLY` and `XXH_INLINE_ALL` +- build: xxHash's implementation transferred into `xxhash.h`. No more need to have `xxhash.c` in the `/include` directory for `XXH_INLINE_ALL` to work +- install: created pkg-config file, by @bket +- install: VCpkg installation instructions, by @LilyWangL +- doc: Highly improved code documentation, by @easyaspi314 +- misc: New test tool in `/tests/collisions`: brute force collision tester for 64-bit hashes + +v0.7.2 +- Fixed collision ratio of `XXH128` for some specific input lengths, reported by @svpv +- Improved `VSX` and `NEON` variants, by @easyaspi314 +- Improved performance of scalar code path (`XXH_VECTOR=0`), by @easyaspi314 +- `xxhsum`: can generate 128-bit hashes with the `-H2` option (note: for experimental purposes only! `XXH128` is not yet frozen) +- `xxhsum`: option `-q` removes status notifications + +v0.7.1 +- Secret first: the algorithm computation can be altered by providing a "secret", which is any blob of bytes, of size >= `XXH3_SECRET_SIZE_MIN`. +- `seed` is still available, and acts as a secret generator +- updated `ARM NEON` variant by @easyaspi314 +- Streaming implementation is available +- Improve compatibility and performance with Visual Studio, with help from @aras-p +- Better integration when using `XXH_INLINE_ALL`: do not pollute host namespace, use its own macros, such as `XXH_ASSERT()`, `XXH_ALIGN`, etc. +- 128-bit variant provides helper functions for comparison of hashes. +- Better `clang` generation of `rotl` instruction, thanks to @easyaspi314 +- `XXH_REROLL` build macro to reduce binary size, by @easyaspi314 +- Improved `cmake` script, by @Mezozoysky +- Full benchmark program provided in `/tests/bench` diff --git a/third_party/xxhash/Doxyfile b/third_party/xxhash/Doxyfile new file mode 100644 index 00000000..f1f8512a --- /dev/null +++ b/third_party/xxhash/Doxyfile @@ -0,0 +1,61 @@ +# Doxygen config for xxHash +DOXYFILE_ENCODING = UTF-8 + +PROJECT_NAME = "xxHash" +PROJECT_NUMBER = "0.8.1" +PROJECT_BRIEF = "Extremely fast non-cryptographic hash function" +OUTPUT_DIRECTORY = doxygen +OUTPUT_LANGUAGE = English + +# We already separate the internal docs. +INTERNAL_DOCS = YES +# Consistency +SORT_MEMBER_DOCS = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES + +# Warnings +QUIET = YES +# Until we document everything +WARN_IF_UNDOCUMENTED = NO + +# TODO: Add the other files. It is just xxhash.h for now. +FILE_PATTERNS = xxhash.h xxh_x86dispatch.c +# Note: xxHash's source files are technically ASCII only. +INPUT_ENCODING = UTF-8 +TAB_SIZE = 4 +MARKDOWN_SUPPORT = YES + +# xxHash is a C library +OPTIMIZE_OUTPUT_FOR_C = YES +# So we can document the internals +EXTRACT_STATIC = YES +# Document the macros +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +# Predefine some macros to clean up the output. +PREDEFINED = "XXH_DOXYGEN=" \ + "XXH_PUBLIC_API=" \ + "XXH_FORCE_INLINE=static inline" \ + "XXH_NO_INLINE=static" \ + "XXH_RESTRICT=restrict" \ + "XSUM_API=" \ + "XXH_STATIC_LINKING_ONLY" \ + "XXH_IMPLEMENTATION" \ + "XXH_PUREF=[[gnu::pure]]" \ + "XXH_CONSTF=[[gnu::const]]" \ + "XXH_MALLOCF=[[gnu::malloc]]" \ + "XXH_ALIGN(N)=alignas(N)" \ + "XXH_ALIGN_MEMBER(align,type)=alignas(align) type" + +# We want HTML docs +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +# Tweak the colors a bit +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_GAMMA = 100 +HTML_COLORSTYLE_SAT = 100 + +# We don't want LaTeX. +GENERATE_LATEX = NO diff --git a/third_party/xxhash/LICENSE b/third_party/xxhash/LICENSE new file mode 100644 index 00000000..e4c5da72 --- /dev/null +++ b/third_party/xxhash/LICENSE @@ -0,0 +1,26 @@ +xxHash Library +Copyright (c) 2012-2021 Yann Collet +All rights reserved. + +BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php) + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/xxhash/Makefile b/third_party/xxhash/Makefile new file mode 100644 index 00000000..3b4e45eb --- /dev/null +++ b/third_party/xxhash/Makefile @@ -0,0 +1,623 @@ +# ################################################################ +# xxHash Makefile +# Copyright (C) 2012-2021 Yann Collet +# +# GPL v2 License +# +# 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 2 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# You can contact the author at: +# - xxHash homepage: https://www.xxhash.com +# - xxHash source repository: https://github.com/Cyan4973/xxHash +# ################################################################ +# xxhsum: provides 32/64 bits hash of one or multiple files, or stdin +# ################################################################ +Q = $(if $(filter 1,$(V) $(VERBOSE)),,@) + +# Version numbers +SED ?= sed +SED_ERE_OPT ?= -E +LIBVER_MAJOR_SCRIPT:=`$(SED) -n '/define XXH_VERSION_MAJOR/s/.*[[:blank:]]\([0-9][0-9]*\).*/\1/p' < xxhash.h` +LIBVER_MINOR_SCRIPT:=`$(SED) -n '/define XXH_VERSION_MINOR/s/.*[[:blank:]]\([0-9][0-9]*\).*/\1/p' < xxhash.h` +LIBVER_PATCH_SCRIPT:=`$(SED) -n '/define XXH_VERSION_RELEASE/s/.*[[:blank:]]\([0-9][0-9]*\).*/\1/p' < xxhash.h` +LIBVER_MAJOR := $(shell echo $(LIBVER_MAJOR_SCRIPT)) +LIBVER_MINOR := $(shell echo $(LIBVER_MINOR_SCRIPT)) +LIBVER_PATCH := $(shell echo $(LIBVER_PATCH_SCRIPT)) +LIBVER := $(LIBVER_MAJOR).$(LIBVER_MINOR).$(LIBVER_PATCH) + +CFLAGS ?= -O3 +DEBUGFLAGS+=-Wall -Wextra -Wconversion -Wcast-qual -Wcast-align -Wshadow \ + -Wstrict-aliasing=1 -Wswitch-enum -Wdeclaration-after-statement \ + -Wstrict-prototypes -Wundef -Wpointer-arith -Wformat-security \ + -Wvla -Wformat=2 -Winit-self -Wfloat-equal -Wwrite-strings \ + -Wredundant-decls -Wstrict-overflow=2 +CFLAGS += $(DEBUGFLAGS) $(MOREFLAGS) +FLAGS = $(CFLAGS) $(CPPFLAGS) +XXHSUM_VERSION = $(LIBVER) +UNAME := $(shell uname) + +# Define *.exe as extension for Windows systems +ifneq (,$(filter Windows%,$(OS))) +EXT =.exe +else +EXT = +endif + +# OS X linker doesn't support -soname, and use different extension +# see: https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/DynamicLibraryDesignGuidelines.html +ifeq ($(UNAME), Darwin) + SHARED_EXT = dylib + SHARED_EXT_MAJOR = $(LIBVER_MAJOR).$(SHARED_EXT) + SHARED_EXT_VER = $(LIBVER).$(SHARED_EXT) + SONAME_FLAGS = -install_name $(LIBDIR)/libxxhash.$(SHARED_EXT_MAJOR) -compatibility_version $(LIBVER_MAJOR) -current_version $(LIBVER) +else + SONAME_FLAGS = -Wl,-soname=libxxhash.$(SHARED_EXT).$(LIBVER_MAJOR) + SHARED_EXT = so + SHARED_EXT_MAJOR = $(SHARED_EXT).$(LIBVER_MAJOR) + SHARED_EXT_VER = $(SHARED_EXT).$(LIBVER) +endif + +LIBXXH = libxxhash.$(SHARED_EXT_VER) + +XXHSUM_SRC_DIR = cli +XXHSUM_SPLIT_SRCS = $(XXHSUM_SRC_DIR)/xxhsum.c \ + $(XXHSUM_SRC_DIR)/xsum_os_specific.c \ + $(XXHSUM_SRC_DIR)/xsum_output.c \ + $(XXHSUM_SRC_DIR)/xsum_sanity_check.c \ + $(XXHSUM_SRC_DIR)/xsum_bench.c +XXHSUM_SPLIT_OBJS = $(XXHSUM_SPLIT_SRCS:.c=.o) +XXHSUM_HEADERS = $(XXHSUM_SRC_DIR)/xsum_config.h \ + $(XXHSUM_SRC_DIR)/xsum_arch.h \ + $(XXHSUM_SRC_DIR)/xsum_os_specific.h \ + $(XXHSUM_SRC_DIR)/xsum_output.h \ + $(XXHSUM_SRC_DIR)/xsum_sanity_check.h \ + $(XXHSUM_SRC_DIR)/xsum_bench.h + +## generate CLI and libraries in release mode (default for `make`) +.PHONY: default +default: DEBUGFLAGS= +default: lib xxhsum_and_links + +.PHONY: all +all: lib xxhsum xxhsum_inlinedXXH + +## xxhsum is the command line interface (CLI) +ifeq ($(DISPATCH),1) +xxhsum: CPPFLAGS += -DXXHSUM_DISPATCH=1 +xxhsum: xxh_x86dispatch.o +endif +xxhsum: xxhash.o $(XXHSUM_SPLIT_OBJS) + $(CC) $(FLAGS) $^ $(LDFLAGS) -o $@$(EXT) + +xxhsum32: CFLAGS += -m32 ## generate CLI in 32-bits mode +xxhsum32: xxhash.c $(XXHSUM_SPLIT_SRCS) ## do not generate object (avoid mixing different ABI) + $(CC) $(FLAGS) $^ $(LDFLAGS) -o $@$(EXT) + +## dispatch only works for x86/x64 systems +dispatch: CPPFLAGS += -DXXHSUM_DISPATCH=1 +dispatch: xxhash.o xxh_x86dispatch.o $(XXHSUM_SPLIT_SRCS) + $(CC) $(FLAGS) $^ $(LDFLAGS) -o $@$(EXT) + +xxhash.o: xxhash.c xxhash.h +xxhsum.o: $(XXHSUM_SRC_DIR)/xxhsum.c $(XXHSUM_HEADERS) \ + xxhash.h xxh_x86dispatch.h +xxh_x86dispatch.o: xxh_x86dispatch.c xxh_x86dispatch.h xxhash.h + +.PHONY: xxhsum_and_links +xxhsum_and_links: xxhsum xxh32sum xxh64sum xxh128sum + +xxh32sum xxh64sum xxh128sum: xxhsum + ln -sf $<$(EXT) $@$(EXT) + +xxhsum_inlinedXXH: CPPFLAGS += -DXXH_INLINE_ALL +xxhsum_inlinedXXH: $(XXHSUM_SPLIT_SRCS) + $(CC) $(FLAGS) $< -o $@$(EXT) + + +# library + +libxxhash.a: ARFLAGS = rcs +libxxhash.a: xxhash.o + $(AR) $(ARFLAGS) $@ $^ + +$(LIBXXH): LDFLAGS += -shared +ifeq (,$(filter Windows%,$(OS))) +$(LIBXXH): CFLAGS += -fPIC +endif +ifeq ($(DISPATCH),1) +$(LIBXXH): xxh_x86dispatch.c +endif +$(LIBXXH): xxhash.c + $(CC) $(FLAGS) $^ $(LDFLAGS) $(SONAME_FLAGS) -o $@ + ln -sf $@ libxxhash.$(SHARED_EXT_MAJOR) + ln -sf $@ libxxhash.$(SHARED_EXT) + +.PHONY: libxxhash +libxxhash: ## generate dynamic xxhash library +libxxhash: $(LIBXXH) + +.PHONY: lib +lib: ## generate static and dynamic xxhash libraries +lib: libxxhash.a libxxhash + +# helper targets + +AWK = awk +GREP = grep +SORT = sort +NM = nm + +.PHONY: list +list: ## list all Makefile targets + $(Q)$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | $(AWK) -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | $(SORT) | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' | xargs + +.PHONY: help +help: ## list documented targets + $(Q)$(GREP) -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + $(SORT) | \ + $(AWK) 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +.PHONY: clean +clean: ## remove all build artifacts + $(Q)$(RM) -r *.dSYM # Mac OS-X specific + $(Q)$(RM) core *.o *.obj *.$(SHARED_EXT) *.$(SHARED_EXT).* *.a libxxhash.pc + $(Q)$(RM) xxhsum$(EXT) xxhsum32$(EXT) xxhsum_inlinedXXH$(EXT) dispatch$(EXT) + $(Q)$(RM) xxh32sum$(EXT) xxh64sum$(EXT) xxh128sum$(EXT) + $(Q)$(RM) $(XXHSUM_SRC_DIR)/*.o $(XXHSUM_SRC_DIR)/*.obj + $(MAKE) -C tests clean + $(MAKE) -C tests/bench clean + $(MAKE) -C tests/collisions clean + @echo cleaning completed + + +# ================================================= +# tests +# ================================================= + +# make check can be run with cross-compiled binaries on emulated environments (qemu user mode) +# by setting $(RUN_ENV) to the target emulation environment +.PHONY: check +check: xxhsum test_sanity ## basic tests for xxhsum CLI, set RUN_ENV for emulated environments + # stdin + $(RUN_ENV) ./xxhsum$(EXT) < xxhash.c + # multiple files + $(RUN_ENV) ./xxhsum$(EXT) xxhash.* + # internal bench + $(RUN_ENV) ./xxhsum$(EXT) -bi0 + # long bench command + $(RUN_ENV) ./xxhsum$(EXT) --benchmark-all -i0 + # bench multiple variants + $(RUN_ENV) ./xxhsum$(EXT) -b1,2,3 -i0 + # file bench + $(RUN_ENV) ./xxhsum$(EXT) -bi0 xxhash.c + # 32-bit + $(RUN_ENV) ./xxhsum$(EXT) -H0 xxhash.c + # 128-bit + $(RUN_ENV) ./xxhsum$(EXT) -H2 xxhash.c + # XXH3 (enforce BSD style) + $(RUN_ENV) ./xxhsum$(EXT) -H3 xxhash.c | grep "XXH3" + # request incorrect variant + $(RUN_ENV) ./xxhsum$(EXT) -H9 xxhash.c ; test $$? -eq 1 + @printf "\n ....... checks completed successfully ....... \n" + +.PHONY: test-unicode +test-unicode: + $(MAKE) -C tests test_unicode + +.PHONY: test_sanity +test_sanity: + $(MAKE) -C tests test_sanity + +.PHONY: test-mem +VALGRIND = valgrind --leak-check=yes --error-exitcode=1 +test-mem: RUN_ENV = $(VALGRIND) +test-mem: xxhsum check + +.PHONY: test32 +test32: xxhsum32 + @echo ---- test 32-bit ---- + ./xxhsum32 -bi0 xxhash.c + +TEST_FILES = xxhsum$(EXT) xxhash.c xxhash.h +.PHONY: test-xxhsum-c +test-xxhsum-c: xxhsum + # xxhsum to/from pipe + ./xxhsum $(TEST_FILES) | ./xxhsum -c - + ./xxhsum -H0 $(TEST_FILES) | ./xxhsum -c - + # xxhsum -c is unable to verify checksum of file from STDIN (#470) + ./xxhsum < README.md > .test.README.md.xxh + ./xxhsum -c .test.README.md.xxh < README.md + # xxhsum -q does not display "Loading" message into stderr (#251) + ! ./xxhsum -q $(TEST_FILES) 2>&1 | grep Loading + # xxhsum does not display "Loading" message into stderr either + ! ./xxhsum $(TEST_FILES) 2>&1 | grep Loading + # Check that xxhsum do display filename that it failed to open. + LC_ALL=C ./xxhsum nonexistent 2>&1 | grep "Error: Could not open 'nonexistent'" + # xxhsum to/from file, shell redirection + ./xxhsum $(TEST_FILES) > .test.xxh64 + ./xxhsum --tag $(TEST_FILES) > .test.xxh64_tag + ./xxhsum --little-endian $(TEST_FILES) > .test.le_xxh64 + ./xxhsum --tag --little-endian $(TEST_FILES) > .test.le_xxh64_tag + ./xxhsum -H0 $(TEST_FILES) > .test.xxh32 + ./xxhsum -H0 --tag $(TEST_FILES) > .test.xxh32_tag + ./xxhsum -H0 --little-endian $(TEST_FILES) > .test.le_xxh32 + ./xxhsum -H0 --tag --little-endian $(TEST_FILES) > .test.le_xxh32_tag + ./xxhsum -H2 $(TEST_FILES) > .test.xxh128 + ./xxhsum -H2 --tag $(TEST_FILES) > .test.xxh128_tag + ./xxhsum -H2 --little-endian $(TEST_FILES) > .test.le_xxh128 + ./xxhsum -H2 --tag --little-endian $(TEST_FILES) > .test.le_xxh128_tag + ./xxhsum -H3 $(TEST_FILES) > .test.xxh3 + ./xxhsum -H3 --tag $(TEST_FILES) > .test.xxh3_tag + ./xxhsum -H3 --little-endian $(TEST_FILES) > .test.le_xxh3 + ./xxhsum -H3 --tag --little-endian $(TEST_FILES) > .test.le_xxh3_tag + ./xxhsum -c .test.xxh* + ./xxhsum -c --little-endian .test.le_xxh* + ./xxhsum -c .test.*_tag + # read list of files from stdin + ./xxhsum -c < .test.xxh32 + ./xxhsum -c < .test.xxh64 + ./xxhsum -c < .test.xxh128 + ./xxhsum -c < .test.xxh3 + cat .test.xxh* | ./xxhsum -c - + # check variant with '*' marker as second separator + $(SED) 's/ / \*/' .test.xxh32 | ./xxhsum -c + # bsd-style output + ./xxhsum --tag xxhsum* | $(GREP) XXH64 + ./xxhsum --tag -H0 xxhsum* | $(GREP) XXH32 + ./xxhsum --tag -H1 xxhsum* | $(GREP) XXH64 + ./xxhsum --tag -H2 xxhsum* | $(GREP) XXH128 + ./xxhsum --tag -H3 xxhsum* | $(GREP) XXH3 + ./xxhsum -H3 xxhsum* | $(GREP) XXH3 # --tag is implicit for H3 + ./xxhsum --tag -H32 xxhsum* | $(GREP) XXH32 + ./xxhsum --tag -H64 xxhsum* | $(GREP) XXH64 + ./xxhsum --tag -H128 xxhsum* | $(GREP) XXH128 + ./xxhsum --tag -H0 --little-endian xxhsum* | $(GREP) XXH32_LE + ./xxhsum --tag -H1 --little-endian xxhsum* | $(GREP) XXH64_LE + ./xxhsum --tag -H2 --little-endian xxhsum* | $(GREP) XXH128_LE + ./xxhsum -H3 --little-endian xxhsum* | $(GREP) XXH3_LE + ./xxhsum --tag -H32 --little-endian xxhsum* | $(GREP) XXH32_LE + ./xxhsum --tag -H64 --little-endian xxhsum* | $(GREP) XXH64_LE + ./xxhsum --tag -H128 --little-endian xxhsum* | $(GREP) XXH128_LE + # check bsd-style + ./xxhsum --tag xxhsum* | ./xxhsum -c + ./xxhsum --tag -H32 --little-endian xxhsum* | ./xxhsum -c + # xxhsum -c warns improperly format lines. + echo '12345678 ' >>.test.xxh32 + ./xxhsum -c .test.xxh32 | $(GREP) improperly + echo '123456789 file' >>.test.xxh64 + ./xxhsum -c .test.xxh64 | $(GREP) improperly + # Expects "FAILED" + echo "0000000000000000 LICENSE" | ./xxhsum -c -; test $$? -eq 1 + echo "00000000 LICENSE" | ./xxhsum -c -; test $$? -eq 1 + # Expects "FAILED open or read" + echo "0000000000000000 test-expects-file-not-found" | ./xxhsum -c -; test $$? -eq 1 + echo "00000000 test-expects-file-not-found" | ./xxhsum -c -; test $$? -eq 1 + @$(RM) .test.* + +.PHONY: test-filename-escape +test-filename-escape: + $(MAKE) -C tests test_filename_escape + +.PHONY: armtest +armtest: clean + @echo ---- test ARM compilation ---- + CC=arm-linux-gnueabi-gcc MOREFLAGS="-Werror -static" $(MAKE) xxhsum + +.PHONY: clangtest +clangtest: clean + @echo ---- test clang compilation ---- + CC=clang MOREFLAGS="-Werror -Wconversion -Wno-sign-conversion" $(MAKE) all + +.PHONY: gcc-og-test +gcc-og-test: clean + @echo ---- test gcc -Og compilation ---- + CFLAGS="-Og -Wall -Wextra -Wundef -Wshadow -Wcast-align -Werror -fPIC" MOREFLAGS="-Werror" $(MAKE) all + +.PHONY: cxxtest +cxxtest: clean + @echo ---- test C++ compilation ---- + CC="$(CXX) -Wno-deprecated" $(MAKE) all CFLAGS="-O3 -Wall -Wextra -Wundef -Wshadow -Wcast-align -Werror -fPIC" + +.PHONY: c90test +ifeq ($(NO_C90_TEST),true) +c90test: + @echo no c90 compatibility test +else +c90test: CPPFLAGS += -DXXH_NO_LONG_LONG +c90test: CFLAGS += -std=c90 -Werror -pedantic +c90test: xxhash.c + @echo ---- test strict C90 compilation [xxh32 only] ---- + $(RM) xxhash.o + $(CC) $(FLAGS) $^ -c + $(NM) xxhash.o | $(GREP) XXH64 ; test $$? -eq 1 + $(RM) xxhash.o +endif + +.PHONY: noxxh3test +noxxh3test: CPPFLAGS += -DXXH_NO_XXH3 +noxxh3test: CFLAGS += -Werror -pedantic -Wno-long-long # XXH64 requires long long support +noxxh3test: OFILE = xxh_noxxh3.o +noxxh3test: xxhash.c + @echo ---- test compilation without XXH3 ---- + $(CC) $(FLAGS) -c $^ -o $(OFILE) + $(NM) $(OFILE) | $(GREP) XXH3_ ; test $$? -eq 1 + $(RM) $(OFILE) + +.PHONY: nostreamtest +nostreamtest: CPPFLAGS += -DXXH_NO_STREAM +nostreamtest: CFLAGS += -Werror -pedantic -Wno-long-long # XXH64 requires long long support +nostreamtest: OFILE = xxh_nostream.o +nostreamtest: xxhash.c + @echo ---- test compilation without streaming ---- + $(CC) $(FLAGS) -c $^ -o $(OFILE) + $(NM) $(OFILE) | $(GREP) update ; test $$? -eq 1 + $(RM) $(OFILE) + +.PHONY: nostdlibtest +nostdlibtest: CPPFLAGS += -DXXH_NO_STDLIB +nostdlibtest: CFLAGS += -Werror -pedantic -Wno-long-long # XXH64 requires long long support +nostdlibtest: OFILE = xxh_nostdlib.o +nostdlibtest: xxhash.c + @echo ---- test compilation without \ ---- + $(CC) $(FLAGS) -c $^ -o $(OFILE) + $(NM) $(OFILE) | $(GREP) "U _free\|U free" ; test $$? -eq 1 + $(RM) $(OFILE) + +.PHONY: usan +usan: CC=clang +usan: CXX=clang++ +usan: ## check CLI runtime for undefined behavior, using clang's sanitizer + @echo ---- check undefined behavior - sanitize ---- + $(MAKE) clean + $(MAKE) test CC=$(CC) CXX=$(CXX) MOREFLAGS="-g -fsanitize=undefined -fno-sanitize-recover=all" + +.PHONY: staticAnalyze +SCANBUILD ?= scan-build +staticAnalyze: clean ## check C source files using $(SCANBUILD) static analyzer + @echo ---- static analyzer - $(SCANBUILD) ---- + CFLAGS="-g -Werror" $(SCANBUILD) --status-bugs -v $(MAKE) all + +CPPCHECK ?= cppcheck +.PHONY: cppcheck +cppcheck: ## check C source files using $(CPPCHECK) static analyzer + @echo ---- static analyzer - $(CPPCHECK) ---- + $(CPPCHECK) . --force --enable=warning,portability,performance,style --error-exitcode=1 > /dev/null + +.PHONY: namespaceTest +namespaceTest: ## ensure XXH_NAMESPACE redefines all public symbols + $(CC) -c xxhash.c + $(CC) -DXXH_NAMESPACE=TEST_ -c xxhash.c -o xxhash2.o + $(CC) xxhash.o xxhash2.o $(XXHSUM_SPLIT_SRCS) -o xxhsum2 # will fail if one namespace missing (symbol collision) + $(RM) *.o xxhsum2 # clean + +MAN = $(XXHSUM_SRC_DIR)/xxhsum.1 +MD2ROFF ?= ronn +MD2ROFF_FLAGS ?= --roff --warnings --manual="User Commands" --organization="xxhsum $(XXHSUM_VERSION)" +$(MAN): $(XXHSUM_SRC_DIR)/xxhsum.1.md xxhash.h + cat $< | $(MD2ROFF) $(MD2ROFF_FLAGS) | $(SED) -n '/^\.\\\".*/!p' > $@ + +.PHONY: man +man: $(MAN) ## generate man page from markdown source + +.PHONY: clean-man +clean-man: + $(RM) xxhsum.1 + +.PHONY: preview-man +preview-man: man + man ./xxhsum.1 + +.PHONY: test +test: DEBUGFLAGS += -DXXH_DEBUGLEVEL=1 +test: all namespaceTest check test-xxhsum-c c90test test-tools noxxh3test nostdlibtest + +.PHONY: test-inline +test-inline: + $(MAKE) -C tests test_multiInclude + +.PHONY: test-all +test-all: CFLAGS += -Werror +test-all: test test32 test-unicode clangtest gcc-og-test cxxtest usan test-inline listL120 trailingWhitespace test-xxh-nnn-sums + +.PHONY: test-tools +test-tools: + CFLAGS=-Werror $(MAKE) -C tests/bench + CFLAGS=-Werror $(MAKE) -C tests/collisions + +.PHONY: test-xxh-nnn-sums +test-xxh-nnn-sums: xxhsum_and_links + ./xxhsum README.md > tmp.xxhsum.out # xxhsum outputs xxh64 + ./xxh32sum README.md > tmp.xxh32sum.out + ./xxh64sum README.md > tmp.xxh64sum.out + ./xxh128sum README.md > tmp.xxh128sum.out + cat tmp.xxhsum.out + cat tmp.xxh32sum.out + cat tmp.xxh64sum.out + cat tmp.xxh128sum.out + ./xxhsum -c tmp.xxhsum.out + ./xxhsum -c tmp.xxh32sum.out + ./xxhsum -c tmp.xxh64sum.out + ./xxhsum -c tmp.xxh128sum.out + ./xxh32sum -c tmp.xxhsum.out ; test $$? -eq 1 # expects "no properly formatted" + ./xxh32sum -c tmp.xxh32sum.out + ./xxh32sum -c tmp.xxh64sum.out ; test $$? -eq 1 # expects "no properly formatted" + ./xxh32sum -c tmp.xxh128sum.out ; test $$? -eq 1 # expects "no properly formatted" + ./xxh64sum -c tmp.xxhsum.out + ./xxh64sum -c tmp.xxh32sum.out ; test $$? -eq 1 # expects "no properly formatted" + ./xxh64sum -c tmp.xxh64sum.out + ./xxh64sum -c tmp.xxh128sum.out ; test $$? -eq 1 # expects "no properly formatted" + ./xxh128sum -c tmp.xxhsum.out ; test $$? -eq 1 # expects "no properly formatted" + ./xxh128sum -c tmp.xxh32sum.out ; test $$? -eq 1 # expects "no properly formatted" + ./xxh128sum -c tmp.xxh64sum.out ; test $$? -eq 1 # expects "no properly formatted" + ./xxh128sum -c tmp.xxh128sum.out + +.PHONY: listL120 +listL120: # extract lines >= 120 characters in *.{c,h}, by Takayuki Matsuoka (note: $$, for Makefile compatibility) + find . -type f -name '*.c' -o -name '*.h' | while read -r filename; do awk 'length > 120 {print FILENAME "(" FNR "): " $$0}' $$filename; done + +.PHONY: trailingWhitespace +trailingWhitespace: + ! $(GREP) -E "`printf '[ \\t]$$'`" cli/*.c cli/*.h cli/*.1 *.c *.h LICENSE Makefile cmake_unofficial/CMakeLists.txt + +.PHONY: lint-unicode +lint-unicode: + ./tests/unicode_lint.sh + +# ========================================================= +# make install is validated only for the following targets +# ========================================================= +ifneq (,$(filter Linux Darwin GNU/kFreeBSD GNU Haiku OpenBSD FreeBSD NetBSD DragonFly SunOS CYGWIN% , $(UNAME))) + +DESTDIR ?= +# directory variables: GNU conventions prefer lowercase +# see https://www.gnu.org/prep/standards/html_node/Makefile-Conventions.html +# support both lower and uppercase (BSD), use uppercase in script +prefix ?= /usr/local +PREFIX ?= $(prefix) +exec_prefix ?= $(PREFIX) +EXEC_PREFIX ?= $(exec_prefix) +libdir ?= $(EXEC_PREFIX)/lib +LIBDIR ?= $(libdir) +includedir ?= $(PREFIX)/include +INCLUDEDIR ?= $(includedir) +bindir ?= $(EXEC_PREFIX)/bin +BINDIR ?= $(bindir) +datarootdir ?= $(PREFIX)/share +mandir ?= $(datarootdir)/man +man1dir ?= $(mandir)/man1 + +ifneq (,$(filter $(UNAME),FreeBSD NetBSD DragonFly)) +PKGCONFIGDIR ?= $(PREFIX)/libdata/pkgconfig +else +PKGCONFIGDIR ?= $(LIBDIR)/pkgconfig +endif + +ifneq (,$(filter $(UNAME),OpenBSD FreeBSD NetBSD DragonFly SunOS)) +MANDIR ?= $(PREFIX)/man/man1 +else +MANDIR ?= $(man1dir) +endif + +ifneq (,$(filter $(UNAME),SunOS)) +INSTALL ?= ginstall +else +INSTALL ?= install +endif + +INSTALL_PROGRAM ?= $(INSTALL) +INSTALL_DATA ?= $(INSTALL) -m 644 +INSTALL_DIR ?= $(INSTALL) -d -m 755 + + +# Escape special symbols by putting each character into its separate class +EXEC_PREFIX_REGEX ?= $(shell echo "$(EXEC_PREFIX)" | $(SED) $(SED_ERE_OPT) -e "s/([^^])/[\1]/g" -e "s/\\^/\\\\^/g") +PREFIX_REGEX ?= $(shell echo "$(PREFIX)" | $(SED) $(SED_ERE_OPT) -e "s/([^^])/[\1]/g" -e "s/\\^/\\\\^/g") + +PCLIBDIR ?= $(shell echo "$(LIBDIR)" | $(SED) -n $(SED_ERE_OPT) -e "s@^$(EXEC_PREFIX_REGEX)(/|$$)@@p") +PCINCDIR ?= $(shell echo "$(INCLUDEDIR)" | $(SED) -n $(SED_ERE_OPT) -e "s@^$(PREFIX_REGEX)(/|$$)@@p") +PCEXECDIR?= $(if $(filter $(PREFIX),$(EXEC_PREFIX)),$$\{prefix\},$(EXEC_PREFIX)) + +ifeq (,$(PCLIBDIR)) +# Additional prefix check is required, since the empty string is technically a +# valid PCLIBDIR +ifeq (,$(shell echo "$(LIBDIR)" | $(SED) -n $(SED_ERE_OPT) -e "\\@^$(EXEC_PREFIX_REGEX)(/|$$)@ p")) +$(error configured libdir ($(LIBDIR)) is outside of exec_prefix ($(EXEC_PREFIX)), can't generate pkg-config file) +endif +endif + +ifeq (,$(PCINCDIR)) +# Additional prefix check is required, since the empty string is technically a +# valid PCINCDIR +ifeq (,$(shell echo "$(INCLUDEDIR)" | $(SED) -n $(SED_ERE_OPT) -e "\\@^$(PREFIX_REGEX)(/|$$)@ p")) +$(error configured includedir ($(INCLUDEDIR)) is outside of prefix ($(PREFIX)), can't generate pkg-config file) +endif +endif + +libxxhash.pc: libxxhash.pc.in + @echo creating pkgconfig + $(Q)$(SED) $(SED_ERE_OPT) -e 's|@PREFIX@|$(PREFIX)|' \ + -e 's|@EXECPREFIX@|$(PCEXECDIR)|' \ + -e 's|@LIBDIR@|$(PCLIBDIR)|' \ + -e 's|@INCLUDEDIR@|$(PCINCDIR)|' \ + -e 's|@VERSION@|$(LIBVER)|' \ + $< > $@ + + +install_libxxhash.a: libxxhash.a + @echo Installing libxxhash.a + $(Q)$(INSTALL_DIR) $(DESTDIR)$(LIBDIR) + $(Q)$(INSTALL_DATA) libxxhash.a $(DESTDIR)$(LIBDIR) + +install_libxxhash: libxxhash + @echo Installing libxxhash + $(Q)$(INSTALL_DIR) $(DESTDIR)$(LIBDIR) + $(Q)$(INSTALL_PROGRAM) $(LIBXXH) $(DESTDIR)$(LIBDIR) + $(Q)ln -sf $(LIBXXH) $(DESTDIR)$(LIBDIR)/libxxhash.$(SHARED_EXT_MAJOR) + $(Q)ln -sf $(LIBXXH) $(DESTDIR)$(LIBDIR)/libxxhash.$(SHARED_EXT) + +install_libxxhash.includes: + $(Q)$(INSTALL) -d -m 755 $(DESTDIR)$(INCLUDEDIR) # includes + $(Q)$(INSTALL_DATA) xxhash.h $(DESTDIR)$(INCLUDEDIR) + $(Q)$(INSTALL_DATA) xxh3.h $(DESTDIR)$(INCLUDEDIR) # for compatibility, will be removed in v0.9.0 +ifeq ($(DISPATCH),1) + $(Q)$(INSTALL_DATA) xxh_x86dispatch.h $(DESTDIR)$(INCLUDEDIR) +endif + +install_libxxhash.pc: libxxhash.pc + @echo Installing pkgconfig + $(Q)$(INSTALL_DIR) $(DESTDIR)$(PKGCONFIGDIR)/ + $(Q)$(INSTALL_DATA) libxxhash.pc $(DESTDIR)$(PKGCONFIGDIR)/ + +install_xxhsum: xxhsum + @echo Installing xxhsum + $(Q)$(INSTALL_DIR) $(DESTDIR)$(BINDIR)/ + $(Q)$(INSTALL_PROGRAM) xxhsum $(DESTDIR)$(BINDIR)/xxhsum + $(Q)ln -sf xxhsum $(DESTDIR)$(BINDIR)/xxh32sum + $(Q)ln -sf xxhsum $(DESTDIR)$(BINDIR)/xxh64sum + $(Q)ln -sf xxhsum $(DESTDIR)$(BINDIR)/xxh128sum + +install_man: + @echo Installing man pages + $(Q)$(INSTALL_DIR) $(DESTDIR)$(MANDIR)/ + $(Q)$(INSTALL_DATA) $(MAN) $(DESTDIR)$(MANDIR)/xxhsum.1 + $(Q)ln -sf xxhsum.1 $(DESTDIR)$(MANDIR)/xxh32sum.1 + $(Q)ln -sf xxhsum.1 $(DESTDIR)$(MANDIR)/xxh64sum.1 + $(Q)ln -sf xxhsum.1 $(DESTDIR)$(MANDIR)/xxh128sum.1 + +.PHONY: install +install: install_libxxhash.a install_libxxhash install_libxxhash.includes install_libxxhash.pc install_xxhsum install_man ## install libraries, CLI, links and man page + @echo xxhash installation completed + +.PHONY: uninstall +uninstall: ## uninstall libraries, CLI, links and man page + $(Q)$(RM) $(DESTDIR)$(LIBDIR)/libxxhash.a + $(Q)$(RM) $(DESTDIR)$(LIBDIR)/libxxhash.$(SHARED_EXT) + $(Q)$(RM) $(DESTDIR)$(LIBDIR)/libxxhash.$(SHARED_EXT_MAJOR) + $(Q)$(RM) $(DESTDIR)$(LIBDIR)/$(LIBXXH) + $(Q)$(RM) $(DESTDIR)$(INCLUDEDIR)/xxhash.h + $(Q)$(RM) $(DESTDIR)$(INCLUDEDIR)/xxh3.h + $(Q)$(RM) $(DESTDIR)$(INCLUDEDIR)/xxh_x86dispatch.h + $(Q)$(RM) $(DESTDIR)$(PKGCONFIGDIR)/libxxhash.pc + $(Q)$(RM) $(DESTDIR)$(BINDIR)/xxh32sum + $(Q)$(RM) $(DESTDIR)$(BINDIR)/xxh64sum + $(Q)$(RM) $(DESTDIR)$(BINDIR)/xxh128sum + $(Q)$(RM) $(DESTDIR)$(BINDIR)/xxhsum + $(Q)$(RM) $(DESTDIR)$(MANDIR)/xxh32sum.1 + $(Q)$(RM) $(DESTDIR)$(MANDIR)/xxh64sum.1 + $(Q)$(RM) $(DESTDIR)$(MANDIR)/xxh128sum.1 + $(Q)$(RM) $(DESTDIR)$(MANDIR)/xxhsum.1 + @echo xxhsum successfully uninstalled + +endif diff --git a/third_party/xxhash/README.md b/third_party/xxhash/README.md new file mode 100644 index 00000000..c5760125 --- /dev/null +++ b/third_party/xxhash/README.md @@ -0,0 +1,271 @@ + +xxHash - Extremely fast hash algorithm +====================================== + +xxHash is an Extremely fast Hash algorithm, processing at RAM speed limits. +Code is highly portable, and produces hashes identical across all platforms (little / big endian). +The library includes the following algorithms : +- XXH32 : generates 32-bit hashes, using 32-bit arithmetic +- XXH64 : generates 64-bit hashes, using 64-bit arithmetic +- XXH3 (since `v0.8.0`): generates 64 or 128-bit hashes, using vectorized arithmetic. + The 128-bit variant is called XXH128. + +All variants successfully complete the [SMHasher](https://code.google.com/p/smhasher/wiki/SMHasher) test suite +which evaluates the quality of hash functions (collision, dispersion and randomness). +Additional tests, which evaluate more thoroughly speed and collision properties of 64-bit hashes, [are also provided](https://github.com/Cyan4973/xxHash/tree/dev/tests). + +|Branch |Status | +|------------|---------| +|release | [![Build Status](https://github.com/Cyan4973/xxHash/actions/workflows/ci.yml/badge.svg?branch=release)](https://github.com/Cyan4973/xxHash/actions?query=branch%3Arelease+) | +|dev | [![Build Status](https://github.com/Cyan4973/xxHash/actions/workflows/ci.yml/badge.svg?branch=dev)](https://github.com/Cyan4973/xxHash/actions?query=branch%3Adev+) | + + +Benchmarks +------------------------- + +The benchmarked reference system uses an Intel i7-9700K cpu, and runs Ubuntu x64 20.04. +The [open source benchmark program] is compiled with `clang` v10.0 using `-O3` flag. + +| Hash Name | Width | Bandwidth (GB/s) | Small Data Velocity | Quality | Comment | +| --------- | ----- | ---------------- | ----- | --- | --- | +| __XXH3__ (SSE2) | 64 | 31.5 GB/s | 133.1 | 10 +| __XXH128__ (SSE2) | 128 | 29.6 GB/s | 118.1 | 10 +| _RAM sequential read_ | N/A | 28.0 GB/s | N/A | N/A | _for reference_ +| City64 | 64 | 22.0 GB/s | 76.6 | 10 +| T1ha2 | 64 | 22.0 GB/s | 99.0 | 9 | Slightly worse [collisions] +| City128 | 128 | 21.7 GB/s | 57.7 | 10 +| __XXH64__ | 64 | 19.4 GB/s | 71.0 | 10 +| SpookyHash | 64 | 19.3 GB/s | 53.2 | 10 +| Mum | 64 | 18.0 GB/s | 67.0 | 9 | Slightly worse [collisions] +| __XXH32__ | 32 | 9.7 GB/s | 71.9 | 10 +| City32 | 32 | 9.1 GB/s | 66.0 | 10 +| Murmur3 | 32 | 3.9 GB/s | 56.1 | 10 +| SipHash | 64 | 3.0 GB/s | 43.2 | 10 +| FNV64 | 64 | 1.2 GB/s | 62.7 | 5 | Poor avalanche properties +| Blake2 | 256 | 1.1 GB/s | 5.1 | 10 | Cryptographic +| SHA1 | 160 | 0.8 GB/s | 5.6 | 10 | Cryptographic but broken +| MD5 | 128 | 0.6 GB/s | 7.8 | 10 | Cryptographic but broken + +[open source benchmark program]: https://github.com/Cyan4973/xxHash/tree/release/tests/bench +[collisions]: https://github.com/Cyan4973/xxHash/wiki/Collision-ratio-comparison#collision-study + +note 1: Small data velocity is a _rough_ evaluation of algorithm's efficiency on small data. For more detailed analysis, please refer to next paragraph. + +note 2: some algorithms feature _faster than RAM_ speed. In which case, they can only reach their full speed potential when input is already in CPU cache (L3 or better). Otherwise, they max out on RAM speed limit. + +### Small data + +Performance on large data is only one part of the picture. +Hashing is also very useful in constructions like hash tables and bloom filters. +In these use cases, it's frequent to hash a lot of small data (starting at a few bytes). +Algorithm's performance can be very different for such scenarios, since parts of the algorithm, +such as initialization or finalization, become fixed cost. +The impact of branch mis-prediction also becomes much more present. + +XXH3 has been designed for excellent performance on both long and small inputs, +which can be observed in the following graph: + +![XXH3, latency, random size](https://user-images.githubusercontent.com/750081/61976089-aedeab00-af9f-11e9-9239-e5375d6c080f.png) + +For a more detailed analysis, please visit the wiki : +https://github.com/Cyan4973/xxHash/wiki/Performance-comparison#benchmarks-concentrating-on-small-data- + +Quality +------------------------- + +Speed is not the only property that matters. +Produced hash values must respect excellent dispersion and randomness properties, +so that any sub-section of it can be used to maximally spread out a table or index, +as well as reduce the amount of collisions to the minimal theoretical level, following the [birthday paradox]. + +`xxHash` has been tested with Austin Appleby's excellent SMHasher test suite, +and passes all tests, ensuring reasonable quality levels. +It also passes extended tests from [newer forks of SMHasher], featuring additional scenarios and conditions. + +Finally, xxHash provides its own [massive collision tester](https://github.com/Cyan4973/xxHash/tree/dev/tests/collisions), +able to generate and compare billions of hashes to test the limits of 64-bit hash algorithms. +On this front too, xxHash features good results, in line with the [birthday paradox]. +A more detailed analysis is documented [in the wiki](https://github.com/Cyan4973/xxHash/wiki/Collision-ratio-comparison). + +[birthday paradox]: https://en.wikipedia.org/wiki/Birthday_problem +[newer forks of SMHasher]: https://github.com/rurban/smhasher + + +### Build modifiers + +The following macros can be set at compilation time to modify libxxhash's behavior. They are generally disabled by default. + +- `XXH_INLINE_ALL`: Make all functions `inline`, with implementations being directly included within `xxhash.h`. + Inlining functions is beneficial for speed on small keys. + It's _extremely effective_ when key length is expressed as _a compile time constant_, + with performance improvements observed in the +200% range . + See [this article](https://fastcompression.blogspot.com/2018/03/xxhash-for-small-keys-impressive-power.html) for details. +- `XXH_PRIVATE_API`: same outcome as `XXH_INLINE_ALL`. Still available for legacy support. + The name underlines that `XXH_*` symbol names will not be exported. +- `XXH_NAMESPACE`: Prefixes all symbols with the value of `XXH_NAMESPACE`. + This macro can only use compilable character set. + Useful to evade symbol naming collisions, + in case of multiple inclusions of xxHash's source code. + Client applications still use the regular function names, + as symbols are automatically translated through `xxhash.h`. +- `XXH_FORCE_ALIGN_CHECK`: Use a faster direct read path when input is aligned. + This option can result in dramatic performance improvement when input to hash is aligned on 32 or 64-bit boundaries, + when running on architectures unable to load memory from unaligned addresses, or suffering a performance penalty from it. + It is (slightly) detrimental on platform with good unaligned memory access performance (same instruction for both aligned and unaligned accesses). + This option is automatically disabled on `x86`, `x64` and `aarch64`, and enabled on all other platforms. +- `XXH_FORCE_MEMORY_ACCESS`: The default method `0` uses a portable `memcpy()` notation. + Method `1` uses a gcc-specific `packed` attribute, which can provide better performance for some targets. + Method `2` forces unaligned reads, which is not standard compliant, but might sometimes be the only way to extract better read performance. + Method `3` uses a byteshift operation, which is best for old compilers which don't inline `memcpy()` or big-endian systems without a byteswap instruction. +- `XXH_VECTOR` : manually select a vector instruction set (default: auto-selected at compilation time). Available instruction sets are `XXH_SCALAR`, `XXH_SSE2`, `XXH_AVX2`, `XXH_AVX512`, `XXH_NEON` and `XXH_VSX`. Compiler may require additional flags to ensure proper support (for example, `gcc` on linux will require `-mavx2` for `AVX2`, and `-mavx512f` for `AVX512`). +- `XXH_NO_PREFETCH` : disable prefetching. Some platforms or situations may perform better without prefetching. XXH3 only. +- `XXH_PREFETCH_DIST` : select prefetching distance. For close-to-metal adaptation to specific hardware platforms. XXH3 only. +- `XXH_NO_STREAM`: Disables the streaming API, limiting it to single shot variants only. +- `XXH_SIZE_OPT`: `0`: default, optimize for speed + `1`: default for `-Os` and `-Oz`: disables some speed hacks for size optimization + `2`: makes code as small as possible, performance may cry +- `XXH_NO_INLINE_HINTS`: By default, xxHash uses `__attribute__((always_inline))` and `__forceinline` to improve performance at the cost of code size. + Defining this macro to 1 will mark all internal functions as `static`, allowing the compiler to decide whether to inline a function or not. + This is very useful when optimizing for smallest binary size, + and is automatically defined when compiling with `-O0`, `-Os`, `-Oz`, or `-fno-inline` on GCC and Clang. + This may also increase performance depending on compiler and architecture. +- `XXH32_ENDJMP`: Switch multi-branch finalization stage of XXH32 by a single jump. + This is generally undesirable for performance, especially when hashing inputs of random sizes. + But depending on exact architecture and compiler, a jump might provide slightly better performance on small inputs. Disabled by default. +- `XXH_NO_STDLIB`: Disable invocation of `` functions, notably `malloc()` and `free()`. + `libxxhash`'s `XXH*_createState()` will always fail and return `NULL`. + But one-shot hashing (like `XXH32()`) or streaming using statically allocated states + still work as expected. + This build flag is useful for embedded environments without dynamic allocation. +- `XXH_STATIC_LINKING_ONLY`: gives access to internal state declaration, required for static allocation. + Incompatible with dynamic linking, due to risks of ABI changes. +- `XXH_NO_XXH3` : removes symbols related to `XXH3` (both 64 & 128 bits) from generated binary. + Useful to reduce binary size, notably for applications which do not employ `XXH3`. +- `XXH_NO_LONG_LONG`: removes compilation of algorithms relying on 64-bit types (`XXH3` and `XXH64`). Only `XXH32` will be compiled. + Useful for targets (architectures and compilers) without 64-bit support. +- `XXH_IMPORT`: MSVC specific: should only be defined for dynamic linking, as it prevents linkage errors. +- `XXH_CPU_LITTLE_ENDIAN`: By default, endianness is determined by a runtime test resolved at compile time. + If, for some reason, the compiler cannot simplify the runtime test, it can cost performance. + It's possible to skip auto-detection and simply state that the architecture is little-endian by setting this macro to 1. + Setting it to 0 states big-endian. +- `XXH_DEBUGLEVEL` : When set to any value >= 1, enables `assert()` statements. + This (slightly) slows down execution, but may help finding bugs during debugging sessions. + +When compiling the Command Line Interface `xxhsum` using `make`, the following environment variables can also be set : +- `DISPATCH=1` : use `xxh_x86dispatch.c`, to automatically select between `scalar`, `sse2`, `avx2` or `avx512` instruction set at runtime, depending on local host. This option is only valid for `x86`/`x64` systems. +- `XXH_1ST_SPEED_TARGET` : select an initial speed target, expressed in MB/s, for the first speed test in benchmark mode. Benchmark will adjust the target at subsequent iterations, but the first test is made "blindly" by targeting this speed. Currently conservatively set to 10 MB/s, to support very slow (emulated) platforms. + +### Building xxHash - Using vcpkg + +You can download and install xxHash using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager: + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + ./vcpkg install xxhash + +The xxHash port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. + +### Building and Using xxHash - tipi.build + +You can work on xxHash and depend on it in your [tipi.build](https://tipi.build) projects by adding the following entry to your `.tipi/deps`: + +```json +{ + "Cyan4973/xxHash": { "@": "v0.8.1" } +} +``` + +An example of such usage can be found in the `/cli` folder of this project which, if built as root project will depend on the release `v0.8.1` of xxHash + + +To contribute to xxHash itself use tipi.build on this repository (change the target name appropriately to `linux` or `macos` or `windows`): + +```bash +tipi . -t --test all +``` + +### Example + +The simplest example calls xxhash 64-bit variant as a one-shot function +generating a hash value from a single buffer, and invoked from a C/C++ program: + +```C +#include "xxhash.h" + + (...) + XXH64_hash_t hash = XXH64(buffer, size, seed); +} +``` + +Streaming variant is more involved, but makes it possible to provide data incrementally: + +```C +#include "stdlib.h" /* abort() */ +#include "xxhash.h" + + +XXH64_hash_t calcul_hash_streaming(FileHandler fh) +{ + /* create a hash state */ + XXH64_state_t* const state = XXH64_createState(); + if (state==NULL) abort(); + + size_t const bufferSize = SOME_SIZE; + void* const buffer = malloc(bufferSize); + if (buffer==NULL) abort(); + + /* Initialize state with selected seed */ + XXH64_hash_t const seed = 0; /* or any other value */ + if (XXH64_reset(state, seed) == XXH_ERROR) abort(); + + /* Feed the state with input data, any size, any number of times */ + (...) + while ( /* some data left */ ) { + size_t const length = get_more_data(buffer, bufferSize, fh); + if (XXH64_update(state, buffer, length) == XXH_ERROR) abort(); + (...) + } + (...) + + /* Produce the final hash value */ + XXH64_hash_t const hash = XXH64_digest(state); + + /* State could be re-used; but in this example, it is simply freed */ + free(buffer); + XXH64_freeState(state); + + return hash; +} +``` + + +### License + +The library files `xxhash.c` and `xxhash.h` are BSD licensed. +The utility `xxhsum` is GPL licensed. + + +### Other programming languages + +Beyond the C reference version, +xxHash is also available from many different programming languages, +thanks to great contributors. +They are [listed here](http://www.xxhash.com/#other-languages). + + +### Packaging status + +Many distributions bundle a package manager +which allows easy xxhash installation as both a `libxxhash` library +and `xxhsum` command line interface. + +[![Packaging status](https://repology.org/badge/vertical-allrepos/xxhash.svg)](https://repology.org/project/xxhash/versions) + + +### Special Thanks + +- Takayuki Matsuoka, aka @t-mat, for creating `xxhsum -c` and great support during early xxh releases +- Mathias Westerdahl, aka @JCash, for introducing the first version of `XXH64` +- Devin Hussey, aka @easyaspi314, for incredible low-level optimizations on `XXH3` and `XXH128` diff --git a/third_party/xxhash/SECURITY.md b/third_party/xxhash/SECURITY.md new file mode 100644 index 00000000..2a8b4c8e --- /dev/null +++ b/third_party/xxhash/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +Security updates are applied only to the latest release. + +## Reporting a Vulnerability + +If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. + +Please disclose it at [security advisory](https://github.com/Cyan4973/xxHash/security/advisories/new). + +This project is maintained by a team of volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure. diff --git a/third_party/xxhash/appveyor.yml b/third_party/xxhash/appveyor.yml new file mode 100644 index 00000000..7aef900f --- /dev/null +++ b/third_party/xxhash/appveyor.yml @@ -0,0 +1,115 @@ +#---------------------------------# +# general configuration # +#---------------------------------# +version: 1.0.{build} +max_jobs: 2 + +#---------------------------------# +# environment configuration # +#---------------------------------# +clone_depth: 2 +environment: + matrix: + - COMPILER: "visual" + ARCH: "x64" + TEST_XXHSUM: "true" + - COMPILER: "visual" + ARCH: "Win32" + TEST_XXHSUM: "true" + - COMPILER: "visual" + ARCH: "Win32" + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 + TEST_XXHSUM: "true" + - COMPILER: "visual" + ARCH: "ARM" +# Below tests are now disabled due to redundancy. +# Their equivalent already runs correctly on Github Actions. +# - COMPILER: "visual" +# ARCH: "x64" +# APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 +# TEST_XXHSUM: "true" +# - COMPILER: "visual" +# ARCH: "ARM64" +# APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 +# # note: ARM64 is not available with Visual Studio 14 2015, which is default for Appveyor + +# The following tests were also flacky on Appveyor, for various reasons. +# - COMPILER: "gcc" +# PLATFORM: "mingw64" +# - COMPILER: "gcc" +# PLATFORM: "mingw32" +# - COMPILER: "gcc" +# PLATFORM: "clang" + +install: + - ECHO Installing %COMPILER% %PLATFORM% %ARCH% + - MKDIR bin + - if [%COMPILER%]==[gcc] SET PATH_ORIGINAL=%PATH% + - if [%COMPILER%]==[gcc] ( + SET "PATH_MINGW32=c:\MinGW\bin;c:\MinGW\usr\bin" && + SET "PATH_MINGW64=c:\msys64\mingw64\bin;c:\msys64\usr\bin" && + COPY C:\MinGW\bin\mingw32-make.exe C:\MinGW\bin\make.exe && + COPY C:\MinGW\bin\gcc.exe C:\MinGW\bin\cc.exe + ) + +#---------------------------------# +# build configuration # +#---------------------------------# +build_script: + - if [%PLATFORM%]==[mingw32] SET PATH=%PATH_MINGW32%;%PATH_ORIGINAL% + - if [%PLATFORM%]==[mingw64] SET PATH=%PATH_MINGW64%;%PATH_ORIGINAL% + - if [%PLATFORM%]==[clang] SET PATH=%PATH_MINGW64%;%PATH_ORIGINAL% + - ECHO *** + - ECHO Building %COMPILER% %PLATFORM% %ARCH% + - ECHO *** + + - if [%COMPILER%]==[gcc] ( + if [%PLATFORM%]==[clang] ( + clang -v + ) ELSE ( + gcc -v + ) + ) + - if [%COMPILER%]==[gcc] ( + echo ----- && + make -v && + echo ----- && + if not [%PLATFORM%]==[clang] ( + if [%PLATFORM%]==[mingw32] ( SET CPPFLAGS=-DPOOL_MT=0 ) && + make -B clean test MOREFLAGS=-Werror + ) ELSE ( + SET CXXFLAGS=--std=c++14 && + make -B clean test CC=clang CXX=clang++ MOREFLAGS="--target=x86_64-w64-mingw32 -Werror -Wno-pass-failed" NO_C90_TEST=true + ) && + make -C tests/bench + ) + # note 1: strict c90 tests with clang fail, due to (erroneous) presence on `inline` keyword in some included system file + # note 2: multi-threading code doesn't work with mingw32, disabled through POOL_MT=0 + # note 3: clang requires C++14 to compile sort because its own code contains c++14-only code + + - if [%COMPILER%]==[visual] ( + cd cmake_unofficial && + cmake . -DCMAKE_BUILD_TYPE=Release -A %ARCH% -DXXHASH_C_FLAGS="/WX" && + cmake --build . --config Release + ) + +#---------------------------------# +# tests configuration # +#---------------------------------# +test_script: + # note: can only run x86 and x64 binaries on Appveyor + # note: if %COMPILER%==gcc, xxhsum was already tested within `make test` + - if [%TEST_XXHSUM%]==[true] ( + ECHO *** && + ECHO Testing %COMPILER% %PLATFORM% %ARCH% && + ECHO *** && + cd Release && + xxhsum.exe -bi1 && + ECHO ------- xxhsum tested ------- + ) + + +#---------------------------------# +# artifacts configuration # +#---------------------------------# +# none yet diff --git a/third_party/xxhash/cli/.tipi/deps b/third_party/xxhash/cli/.tipi/deps new file mode 100644 index 00000000..653dbe10 --- /dev/null +++ b/third_party/xxhash/cli/.tipi/deps @@ -0,0 +1,3 @@ +{ + "Cyan4973/xxHash": { } +} \ No newline at end of file diff --git a/third_party/xxhash/cli/.tipi/opts b/third_party/xxhash/cli/.tipi/opts new file mode 100644 index 00000000..e69de29b diff --git a/third_party/xxhash/cli/COPYING b/third_party/xxhash/cli/COPYING new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/third_party/xxhash/cli/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 2 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/third_party/xxhash/cli/README.md b/third_party/xxhash/cli/README.md new file mode 100644 index 00000000..a60a945f --- /dev/null +++ b/third_party/xxhash/cli/README.md @@ -0,0 +1,4 @@ +This directory contains source code dedicated to the `xxhsum` command line utility, +which is a user program of `libxxhash`. + +Note that, in contrast with the library `libxxhash`, the command line utility `xxhsum` ships with GPLv2 license. diff --git a/third_party/xxhash/cli/xsum_arch.h b/third_party/xxhash/cli/xsum_arch.h new file mode 100644 index 00000000..17d332cb --- /dev/null +++ b/third_party/xxhash/cli/xsum_arch.h @@ -0,0 +1,159 @@ +/* + * xxhsum - Command line interface for xxhash algorithms + * Copyright (C) 2013-2021 Yann Collet + * + * GPL v2 License + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can contact the author at: + * - xxHash homepage: https://www.xxhash.com + * - xxHash source repository: https://github.com/Cyan4973/xxHash + */ + +/* + * Checks for predefined macros by the compiler to try and get both the arch + * and the compiler version. + */ +#ifndef XSUM_ARCH_H +#define XSUM_ARCH_H + +#include "xsum_config.h" + +#define XSUM_LIB_VERSION XXH_VERSION_MAJOR.XXH_VERSION_MINOR.XXH_VERSION_RELEASE +#define XSUM_QUOTE(str) #str +#define XSUM_EXPAND_AND_QUOTE(str) XSUM_QUOTE(str) +#define XSUM_PROGRAM_VERSION XSUM_EXPAND_AND_QUOTE(XSUM_LIB_VERSION) + + +/* Show compiler versions in WELCOME_MESSAGE. XSUM_CC_VERSION_FMT will return the printf specifiers, + * and VERSION will contain the comma separated list of arguments to the XSUM_CC_VERSION_FMT string. */ +#if defined(__clang_version__) +/* Clang does its own thing. */ +# ifdef __apple_build_version__ +# define XSUM_CC_VERSION_FMT "Apple Clang %s" +# else +# define XSUM_CC_VERSION_FMT "Clang %s" +# endif +# define XSUM_CC_VERSION __clang_version__ +#elif defined(__VERSION__) +/* GCC and ICC */ +# define XSUM_CC_VERSION_FMT "%s" +# ifdef __INTEL_COMPILER /* icc adds its prefix */ +# define XSUM_CC_VERSION __VERSION__ +# else /* assume GCC */ +# define XSUM_CC_VERSION "GCC " __VERSION__ +# endif +#elif defined(_MSC_FULL_VER) && defined(_MSC_BUILD) +/* + * MSVC + * "For example, if the version number of the Visual C++ compiler is + * 15.00.20706.01, the _MSC_FULL_VER macro evaluates to 150020706." + * + * https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=vs-2017 + */ +# define XSUM_CC_VERSION_FMT "MSVC %02i.%02i.%05i.%02i" +# define XSUM_CC_VERSION _MSC_FULL_VER / 10000000 % 100, _MSC_FULL_VER / 100000 % 100, _MSC_FULL_VER % 100000, _MSC_BUILD +#elif defined(_MSC_VER) /* old MSVC */ +# define XSUM_CC_VERSION_FMT "MSVC %02i.%02i" +# define XSUM_CC_VERSION _MSC_VER / 100, _MSC_VER % 100 +#elif defined(__TINYC__) +/* tcc stores its version in the __TINYC__ macro. */ +# define XSUM_CC_VERSION_FMT "tcc %i.%i.%i" +# define XSUM_CC_VERSION __TINYC__ / 10000 % 100, __TINYC__ / 100 % 100, __TINYC__ % 100 +#else +# define XSUM_CC_VERSION_FMT "%s" +# define XSUM_CC_VERSION "unknown compiler" +#endif + +/* makes the next part easier */ +#if (defined(__x86_64__) || defined(_M_AMD64) || defined(_M_X64)) && !defined(_M_ARM64EC) +# define XSUM_ARCH_X64 1 +# define XSUM_ARCH_X86 "x86_64" +#elif defined(__i386__) || defined(_M_IX86) || defined(_M_IX86_FP) +# define XSUM_ARCH_X86 "i386" +#endif + +/* Try to detect the architecture. */ +#if defined(XSUM_ARCH_X86) +# if defined(XXHSUM_DISPATCH) +# define XSUM_ARCH XSUM_ARCH_X86 " autoVec" +# elif defined(__AVX512F__) +# define XSUM_ARCH XSUM_ARCH_X86 " + AVX512" +# elif defined(__AVX2__) +# define XSUM_ARCH XSUM_ARCH_X86 " + AVX2" +# elif defined(__AVX__) +# define XSUM_ARCH XSUM_ARCH_X86 " + AVX" +# elif defined(_M_X64) || defined(_M_AMD64) || defined(__x86_64__) \ + || defined(__SSE2__) || (defined(_M_IX86_FP) && _M_IX86_FP == 2) +# define XSUM_ARCH XSUM_ARCH_X86 " + SSE2" +# else +# define XSUM_ARCH XSUM_ARCH_X86 +# endif +#elif defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64) || defined(_M_ARM64EC) +# define XSUM_ARCH "aarch64 + NEON" +#elif defined(__arm__) || defined(__thumb__) || defined(__thumb2__) || defined(_M_ARM) +/* ARM has a lot of different features that can change xxHash significantly. */ +# ifdef __ARM_ARCH +# define XSUM_ARCH_ARM_VER XSUM_EXPAND_AND_QUOTE(__ARM_ARCH) +# else +# define XSUM_ARCH_ARM_VER XSUM_EXPAND_AND_QUOTE(_M_ARM) +# endif +# if defined(_M_ARM) /* windows arm is always thumb-2 */ \ + || defined(__thumb2__) || (defined(__thumb__) && (__thumb__ == 2 || __ARM_ARCH >= 7)) +# define XSUM_ARCH_THUMB " Thumb-2" +# elif defined(__thumb__) +# define XSUM_ARCH_THUMB " Thumb-1" +# else +# define XSUM_ARCH_THUMB "" +# endif +/* ARMv7 has unaligned by default */ +# if defined(__ARM_FEATURE_UNALIGNED) || __ARM_ARCH >= 7 || defined(_M_ARM) +# define XSUM_ARCH_UNALIGNED " + unaligned" +# else +# define XSUM_ARCH_UNALIGNED "" +# endif +# if defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(_M_ARM) +# define XSUM_ARCH_NEON " + NEON" +# else +# define XSUM_ARCH_NEON "" +# endif +# define XSUM_ARCH "ARMv" XSUM_ARCH_ARM_VER XSUM_ARCH_THUMB XSUM_ARCH_NEON XSUM_ARCH_UNALIGNED +#elif defined(__powerpc64__) || defined(__ppc64__) || defined(__PPC64__) +# if defined(__GNUC__) && defined(__POWER9_VECTOR__) +# define XSUM_ARCH "ppc64 + POWER9 vector" +# elif defined(__GNUC__) && defined(__POWER8_VECTOR__) +# define XSUM_ARCH "ppc64 + POWER8 vector" +# else +# define XSUM_ARCH "ppc64" +# endif +#elif defined(__powerpc__) || defined(__ppc__) || defined(__PPC__) +# define XSUM_ARCH "ppc" +#elif defined(__AVR) +# define XSUM_ARCH "AVR" +#elif defined(__mips64) +# define XSUM_ARCH "mips64" +#elif defined(__mips) +# define XSUM_ARCH "mips" +#elif defined(__s390x__) +# define XSUM_ARCH "s390x" +#elif defined(__s390__) +# define XSUM_ARCH "s390" +#else +# define XSUM_ARCH "unknown" +#endif + + +#endif /* XSUM_ARCH_H */ diff --git a/third_party/xxhash/cli/xsum_bench.c b/third_party/xxhash/cli/xsum_bench.c new file mode 100644 index 00000000..dfea66b7 --- /dev/null +++ b/third_party/xxhash/cli/xsum_bench.c @@ -0,0 +1,448 @@ +/* + * xsum_bench - Benchmark functions for xxhsum + * Copyright (C) 2013-2021 Yann Collet + * + * GPL v2 License + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can contact the author at: + * - xxHash homepage: https://www.xxhash.com + * - xxHash source repository: https://github.com/Cyan4973/xxHash + */ + +#include "xsum_output.h" /* XSUM_logLevel */ +#include "xsum_bench.h" +#include "xsum_sanity_check.h" /* XSUM_fillTestBuffer */ +#include "xsum_os_specific.h" /* XSUM_getFileSize */ +#ifndef XXH_STATIC_LINKING_ONLY +# define XXH_STATIC_LINKING_ONLY +#endif +#include "../xxhash.h" +#ifdef XXHSUM_DISPATCH +# include "../xxh_x86dispatch.h" /* activate _dispatch() redirectors */ +#endif + +#include /* malloc, free */ +#include +#include /* strlen, memcpy */ +#include /* clock_t, clock, CLOCKS_PER_SEC */ +#include /* errno */ + +#define TIMELOOP_S 1 +#define TIMELOOP (TIMELOOP_S * CLOCKS_PER_SEC) /* target timing per iteration */ +#define TIMELOOP_MIN (TIMELOOP / 2) /* minimum timing to validate a result */ + +/* Each benchmark iteration attempts to match TIMELOOP (1 second). + * The nb of loops is adjusted at each iteration to reach that target. + * However, initially, there is no information, so 1st iteration blindly targets an arbitrary speed. + * If it's too small, it will be adjusted, and a new attempt will be made. + * But if it's too large, the first iteration can be very long, + * before being fixed at second attempt. + * So prefer starting with small speed targets. + * XXH_1ST_SPEED_TARGET is defined in MB/s */ +#ifndef XXH_1ST_SPEED_TARGET +# define XXH_1ST_SPEED_TARGET 10 +#endif + +#define MAX_MEM (2 GB - 64 MB) + +static clock_t XSUM_clockSpan( clock_t start ) +{ + return clock() - start; /* works even if overflow; Typical max span ~ 30 mn */ +} + +static size_t XSUM_findMaxMem(XSUM_U64 requiredMem) +{ + size_t const step = 64 MB; + void* testmem = NULL; + + requiredMem = (((requiredMem >> 26) + 1) << 26); + requiredMem += 2*step; + if (requiredMem > MAX_MEM) requiredMem = MAX_MEM; + + while (!testmem) { + if (requiredMem > step) requiredMem -= step; + else requiredMem >>= 1; + testmem = malloc ((size_t)requiredMem); + } + free (testmem); + + /* keep some space available */ + if (requiredMem > step) requiredMem -= step; + else requiredMem >>= 1; + + return (size_t)requiredMem; +} + +/* + * A secret buffer used for benchmarking XXH3's withSecret variants. + * + * In order for the bench to be realistic, the secret buffer would need to be + * pre-generated. + * + * Adding a pointer to the parameter list would be messy. + */ +static XSUM_U8 g_benchSecretBuf[XXH3_SECRET_SIZE_MIN]; + +/* + * Wrappers for the benchmark. + * + * If you would like to add other hashes to the bench, create a wrapper and add + * it to the g_hashesToBench table. It will automatically be added. + */ +typedef XSUM_U32 (*hashFunction)(const void* buffer, size_t bufferSize, XSUM_U32 seed); + +static XSUM_U32 localXXH32(const void* buffer, size_t bufferSize, XSUM_U32 seed) +{ + return XXH32(buffer, bufferSize, seed); +} +static XSUM_U32 localXXH32_stream(const void* buffer, size_t bufferSize, XSUM_U32 seed) +{ + XXH32_state_t state; + (void)seed; + XXH32_reset(&state, seed); + XXH32_update(&state, buffer, bufferSize); + return (XSUM_U32)XXH32_digest(&state); +} +static XSUM_U32 localXXH64(const void* buffer, size_t bufferSize, XSUM_U32 seed) +{ + return (XSUM_U32)XXH64(buffer, bufferSize, seed); +} +static XSUM_U32 localXXH64_stream(const void* buffer, size_t bufferSize, XSUM_U32 seed) +{ + XXH64_state_t state; + (void)seed; + XXH64_reset(&state, seed); + XXH64_update(&state, buffer, bufferSize); + return (XSUM_U32)XXH64_digest(&state); +} +static XSUM_U32 localXXH3_64b(const void* buffer, size_t bufferSize, XSUM_U32 seed) +{ + (void)seed; + return (XSUM_U32)XXH3_64bits(buffer, bufferSize); +} +static XSUM_U32 localXXH3_64b_seeded(const void* buffer, size_t bufferSize, XSUM_U32 seed) +{ + return (XSUM_U32)XXH3_64bits_withSeed(buffer, bufferSize, seed); +} +static XSUM_U32 localXXH3_64b_secret(const void* buffer, size_t bufferSize, XSUM_U32 seed) +{ + (void)seed; + return (XSUM_U32)XXH3_64bits_withSecret(buffer, bufferSize, g_benchSecretBuf, sizeof(g_benchSecretBuf)); +} +static XSUM_U32 localXXH3_128b(const void* buffer, size_t bufferSize, XSUM_U32 seed) +{ + (void)seed; + return (XSUM_U32)(XXH3_128bits(buffer, bufferSize).low64); +} +static XSUM_U32 localXXH3_128b_seeded(const void* buffer, size_t bufferSize, XSUM_U32 seed) +{ + return (XSUM_U32)(XXH3_128bits_withSeed(buffer, bufferSize, seed).low64); +} +static XSUM_U32 localXXH3_128b_secret(const void* buffer, size_t bufferSize, XSUM_U32 seed) +{ + (void)seed; + return (XSUM_U32)(XXH3_128bits_withSecret(buffer, bufferSize, g_benchSecretBuf, sizeof(g_benchSecretBuf)).low64); +} +static XSUM_U32 localXXH3_stream(const void* buffer, size_t bufferSize, XSUM_U32 seed) +{ + XXH3_state_t state; + (void)seed; + XXH3_64bits_reset(&state); + XXH3_64bits_update(&state, buffer, bufferSize); + return (XSUM_U32)XXH3_64bits_digest(&state); +} +static XSUM_U32 localXXH3_stream_seeded(const void* buffer, size_t bufferSize, XSUM_U32 seed) +{ + XXH3_state_t state; + XXH3_INITSTATE(&state); + XXH3_64bits_reset_withSeed(&state, (XXH64_hash_t)seed); + XXH3_64bits_update(&state, buffer, bufferSize); + return (XSUM_U32)XXH3_64bits_digest(&state); +} +static XSUM_U32 localXXH128_stream(const void* buffer, size_t bufferSize, XSUM_U32 seed) +{ + XXH3_state_t state; + (void)seed; + XXH3_128bits_reset(&state); + XXH3_128bits_update(&state, buffer, bufferSize); + return (XSUM_U32)(XXH3_128bits_digest(&state).low64); +} +static XSUM_U32 localXXH128_stream_seeded(const void* buffer, size_t bufferSize, XSUM_U32 seed) +{ + XXH3_state_t state; + XXH3_INITSTATE(&state); + XXH3_128bits_reset_withSeed(&state, (XXH64_hash_t)seed); + XXH3_128bits_update(&state, buffer, bufferSize); + return (XSUM_U32)(XXH3_128bits_digest(&state).low64); +} + + +typedef struct { + const char* name; + hashFunction func; +} hashInfo; + +static const hashInfo g_hashesToBench[] = { + { "XXH32", &localXXH32 }, + { "XXH64", &localXXH64 }, + { "XXH3_64b", &localXXH3_64b }, + { "XXH3_64b w/seed", &localXXH3_64b_seeded }, + { "XXH3_64b w/secret", &localXXH3_64b_secret }, + { "XXH128", &localXXH3_128b }, + { "XXH128 w/seed", &localXXH3_128b_seeded }, + { "XXH128 w/secret", &localXXH3_128b_secret }, + { "XXH32_stream", &localXXH32_stream }, + { "XXH64_stream", &localXXH64_stream }, + { "XXH3_stream", &localXXH3_stream }, + { "XXH3_stream w/seed",&localXXH3_stream_seeded }, + { "XXH128_stream", &localXXH128_stream }, + { "XXH128_stream w/seed",&localXXH128_stream_seeded }, +}; +#define NB_HASHFUNC (sizeof(g_hashesToBench) / sizeof(*g_hashesToBench)) + +#define NB_TESTFUNC (1 + 2 * NB_HASHFUNC) +int const g_nbTestFunctions = NB_TESTFUNC; +char g_testIDs[NB_TESTFUNC] = { 0 }; +const char k_testIDs_default[NB_TESTFUNC] = { 0, + 1 /*XXH32*/, 0, + 1 /*XXH64*/, 0, + 1 /*XXH3*/, 0, 0, 0, 0, 0, + 1 /*XXH128*/ }; + +int g_nbIterations = NBLOOPS_DEFAULT; +#define HASHNAME_MAX 29 +static void XSUM_benchHash(hashFunction h, const char* hName, int testID, + const void* buffer, size_t bufferSize) +{ + XSUM_U32 nbh_perIteration = (XSUM_U32)((XXH_1ST_SPEED_TARGET MB) / (bufferSize+1)) + 1; + int iterationNb, nbIterations = g_nbIterations + !g_nbIterations /* min 1 */; + double fastestH = 100000000.; + assert(HASHNAME_MAX > 2); + XSUM_logVerbose(2, "\r%80s\r", ""); /* Clean display line */ + + for (iterationNb = 1; iterationNb <= nbIterations; iterationNb++) { + XSUM_U32 r=0; + clock_t cStart; + + XSUM_logVerbose(2, "%2i-%-*.*s : %10u ->\r", + iterationNb, + HASHNAME_MAX, HASHNAME_MAX, hName, + (unsigned)bufferSize); + cStart = clock(); + while (clock() == cStart); /* starts clock() at its exact beginning */ + cStart = clock(); + + { XSUM_U32 u; + for (u=0; u (double)(4000U<<20)) nbh_perSecond = (double)(4000U<<20); /* avoid overflow */ + nbh_perIteration = (XSUM_U32)nbh_perSecond; + } + /* g_nbIterations==0 => quick evaluation, no claim of accuracy */ + if (g_nbIterations>0) { + iterationNb--; /* new round for a more accurate speed evaluation */ + continue; + } + } + if (ticksPerHash < fastestH) fastestH = ticksPerHash; + if (fastestH>0.) { /* avoid div by zero */ + XSUM_logVerbose(2, "%2i-%-*.*s : %10u -> %8.0f it/s (%7.1f MB/s) \r", + iterationNb, + HASHNAME_MAX, HASHNAME_MAX, hName, + (unsigned)bufferSize, + (double)1 / fastestH, + ((double)bufferSize / (1 MB)) / fastestH); + } } + { double nbh_perSecond = (1 / fastestH) + 1; + if (nbh_perSecond > (double)(4000U<<20)) nbh_perSecond = (double)(4000U<<20); /* avoid overflow */ + nbh_perIteration = (XSUM_U32)nbh_perSecond; + } + } + XSUM_logVerbose(1, "%2i#%-*.*s : %10u -> %8.0f it/s (%7.1f MB/s) \n", + testID, + HASHNAME_MAX, HASHNAME_MAX, hName, + (unsigned)bufferSize, + (double)1 / fastestH, + ((double)bufferSize / (1 MB)) / fastestH); + if (XSUM_logLevel<1) + XSUM_logVerbose(0, "%u, ", (unsigned)((double)1 / fastestH)); +} + + +/* + * Allocates a string containing s1 and s2 concatenated. Acts like strdup. + * The result must be freed. + */ +static char* XSUM_strcatDup(const char* s1, const char* s2) +{ + assert(s1 != NULL); + assert(s2 != NULL); + { size_t len1 = strlen(s1); + size_t len2 = strlen(s2); + char* buf = (char*)malloc(len1 + len2 + 1); + if (buf != NULL) { + /* strcpy(buf, s1) */ + memcpy(buf, s1, len1); + /* strcat(buf, s2) */ + memcpy(buf + len1, s2, len2 + 1); + } + return buf; + } +} + + +/*! + * XSUM_benchMem(): + * buffer: Must be 16-byte aligned. + * The real allocated size of buffer is supposed to be >= (bufferSize+3). + * returns: 0 on success, 1 if error (invalid mode selected) + */ +static void XSUM_benchMem(const void* buffer, size_t bufferSize) +{ + assert((((size_t)buffer) & 15) == 0); /* ensure alignment */ + XSUM_fillTestBuffer(g_benchSecretBuf, sizeof(g_benchSecretBuf)); + { int i; + for (i = 1; i < (int)NB_TESTFUNC; i++) { + int const hashFuncID = (i-1) / 2; + assert(g_hashesToBench[hashFuncID].name != NULL); + if (g_testIDs[i] == 0) continue; + /* aligned */ + if ((i % 2) == 1) { + XSUM_benchHash(g_hashesToBench[hashFuncID].func, g_hashesToBench[hashFuncID].name, i, buffer, bufferSize); + } + /* unaligned */ + if ((i % 2) == 0) { + /* Append "unaligned". */ + char* const hashNameBuf = XSUM_strcatDup(g_hashesToBench[hashFuncID].name, " unaligned"); + assert(hashNameBuf != NULL); + XSUM_benchHash(g_hashesToBench[hashFuncID].func, hashNameBuf, i, ((const char*)buffer)+3, bufferSize); + free(hashNameBuf); + } + } } +} + +static size_t XSUM_selectBenchedSize(const char* fileName) +{ + XSUM_U64 const inFileSize = XSUM_getFileSize(fileName); + size_t benchedSize = (size_t) XSUM_findMaxMem(inFileSize); + if ((XSUM_U64)benchedSize > inFileSize) benchedSize = (size_t)inFileSize; + if (benchedSize < inFileSize) { + XSUM_log("Not enough memory for '%s' full size; testing %i MB only...\n", fileName, (int)(benchedSize>>20)); + } + return benchedSize; +} + + +int XSUM_benchFiles(const char* fileNamesTable[], int nbFiles) +{ + int fileIdx; + for (fileIdx=0; fileIdx 10 KB) { + XSUM_logVerbose(1, "%u KB", (unsigned)(keySize >> 10)); + } else { + XSUM_logVerbose(1, "%u bytes", (unsigned)keySize); + } + XSUM_logVerbose(1, "... \n"); + + XSUM_benchMem(alignedBuffer, keySize); + free(buffer); + } + return 0; +} diff --git a/third_party/xxhash/cli/xsum_bench.h b/third_party/xxhash/cli/xsum_bench.h new file mode 100644 index 00000000..6faaec8c --- /dev/null +++ b/third_party/xxhash/cli/xsum_bench.h @@ -0,0 +1,51 @@ +/* + * xsum_bench - Benchmark functions for xxhsum + * Copyright (C) 2013-2021 Yann Collet + * + * GPL v2 License + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can contact the author at: + * - xxHash homepage: https://www.xxhash.com + * - xxHash source repository: https://github.com/Cyan4973/xxHash + */ + +#ifndef XSUM_BENCH_H +#define XSUM_BENCH_H + +#include /* size_t */ + +#define NBLOOPS_DEFAULT 3 /* Default number of benchmark iterations */ + +extern int const g_nbTestFunctions; +extern char g_testIDs[]; /* size : g_nbTestFunctions */ +extern const char k_testIDs_default[]; +extern int g_nbIterations; + +int XSUM_benchInternal(size_t keySize); +int XSUM_benchFiles(const char* fileNamesTable[], int nbFiles); + + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* XSUM_BENCH_H */ diff --git a/third_party/xxhash/cli/xsum_config.h b/third_party/xxhash/cli/xsum_config.h new file mode 100644 index 00000000..eec5528d --- /dev/null +++ b/third_party/xxhash/cli/xsum_config.h @@ -0,0 +1,214 @@ +/* + * xxhsum - Command line interface for xxhash algorithms + * Copyright (C) 2013-2021 Yann Collet + * + * GPL v2 License + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can contact the author at: + * - xxHash homepage: https://www.xxhash.com + * - xxHash source repository: https://github.com/Cyan4973/xxHash + */ + +/* + * This contains various configuration parameters and feature detection for + * xxhsum. + * + * Similar to config.h in Autotools, this should be the first header included. + */ + +#ifndef XSUM_CONFIG_H +#define XSUM_CONFIG_H + + +/* ************************************ + * Compiler Options + **************************************/ +/* + * Disable Visual C's warnings when using the "insecure" CRT functions instead + * of the "secure" _s functions. + * + * These functions are not portable, and aren't necessary if you are using the + * original functions properly. + */ +#if defined(_MSC_VER) || defined(_WIN32) +# ifndef _CRT_SECURE_NO_WARNINGS +# define _CRT_SECURE_NO_WARNINGS +# endif +#endif + +/* Under Linux at least, pull in the *64 commands */ +#ifndef _LARGEFILE64_SOURCE +# define _LARGEFILE64_SOURCE +#endif +#ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +#endif + +/* + * So we can use __attribute__((__format__)) + */ +#ifdef __GNUC__ +# define XSUM_ATTRIBUTE(x) __attribute__(x) +#else +# define XSUM_ATTRIBUTE(x) +#endif + +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) /* UNIX-like OS */ \ + || defined(__midipix__) || defined(__VMS)) +# if (defined(__APPLE__) && defined(__MACH__)) || defined(__SVR4) || defined(_AIX) || defined(__hpux) /* POSIX.1-2001 (SUSv3) conformant */ \ + || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) /* BSD distros */ +# define XSUM_PLATFORM_POSIX_VERSION 200112L +# else +# if defined(__linux__) || defined(__linux) +# ifndef _POSIX_C_SOURCE +# define _POSIX_C_SOURCE 200112L /* use feature test macro */ +# endif +# endif +# include /* declares _POSIX_VERSION */ +# if defined(_POSIX_VERSION) /* POSIX compliant */ +# define XSUM_PLATFORM_POSIX_VERSION _POSIX_VERSION +# else +# define XSUM_PLATFORM_POSIX_VERSION 0 +# endif +# endif +#endif +#if !defined(XSUM_PLATFORM_POSIX_VERSION) +# define XSUM_PLATFORM_POSIX_VERSION -1 +#endif + +#if !defined(S_ISREG) +# define S_ISREG(x) (((x) & S_IFMT) == S_IFREG) +#endif + + +/* ************************************ + * Windows helpers + **************************************/ + +/* + * Whether to use the Windows UTF-16 APIs instead of the portable libc 8-bit + * ("ANSI") APIs. + * + * Windows is not UTF-8 clean by default, and the only way to access every file + * on the OS is to use UTF-16. + * + * Do note that xxhsum uses UTF-8 internally and only uses UTF-16 for command + * line arguments, console I/O, and opening files. + * + * Additionally, this guarantees all piped output is UTF-8. + */ +#if defined(XSUM_WIN32_USE_WCHAR) && !defined(_WIN32) +/* We use Windows APIs, only use this on Windows. */ +# undef XSUM_WIN32_USE_WCHAR +#endif + +#ifndef XSUM_WIN32_USE_WCHAR +# if defined(_WIN32) +# include +# if WCHAR_MAX == 0xFFFFU /* UTF-16 wchar_t */ +# define XSUM_WIN32_USE_WCHAR 1 +# else +# define XSUM_WIN32_USE_WCHAR 0 +# endif +# else +# define XSUM_WIN32_USE_WCHAR 0 +# endif +#endif + +#if !XSUM_WIN32_USE_WCHAR +/* + * It doesn't make sense to have one without the other. + * Due to XSUM_WIN32_USE_WCHAR being undef'd, this also handles + * non-WIN32 platforms. + */ +# undef XSUM_WIN32_USE_WMAIN +# define XSUM_WIN32_USE_WMAIN 0 +#else +/* + * Whether to use wmain() or main(). + * + * wmain() is preferred because we don't have to mess with internal hidden + * APIs. + * + * It always works on MSVC, but in MinGW, it only works on MinGW-w64 with the + * -municode flag. + * + * Therefore we have to use main() -- there is no better option. + */ +# ifndef XSUM_WIN32_USE_WMAIN +# if defined(_UNICODE) || defined(UNICODE) /* MinGW -municode */ \ + || defined(_MSC_VER) /* MSVC */ +# define XSUM_WIN32_USE_WMAIN 1 +# else +# define XSUM_WIN32_USE_WMAIN 0 +# endif +# endif +/* + * It is always good practice to define these to prevent accidental use of the + * ANSI APIs, even if the program primarily uses UTF-8. + */ +# ifndef _UNICODE +# define _UNICODE +# endif +# ifndef UNICODE +# define UNICODE +# endif +#endif /* XSUM_WIN32_USE_WCHAR */ + +#ifndef XSUM_API +# ifdef XXH_INLINE_ALL +# define XSUM_API static +# else +# define XSUM_API +# endif +#endif + +#ifndef XSUM_NO_TESTS +# define XSUM_NO_TESTS 0 +#endif + +/* *************************** + * Basic types + * ***************************/ + +#if defined(__cplusplus) /* C++ */ \ + || (defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) /* C99 */ +# include + typedef uint8_t XSUM_U8; + typedef uint32_t XSUM_U32; + typedef uint64_t XSUM_U64; +# else +# include + typedef unsigned char XSUM_U8; +# if UINT_MAX == 0xFFFFFFFFUL + typedef unsigned int XSUM_U32; +# else + typedef unsigned long XSUM_U32; +# endif + typedef unsigned long long XSUM_U64; +#endif /* not C++/C99 */ + +/* *************************** + * Common constants + * ***************************/ + +#define KB *( 1<<10) +#define MB *( 1<<20) +#define GB *(1U<<30) + + +#endif /* XSUM_CONFIG_H */ diff --git a/third_party/xxhash/cli/xsum_os_specific.c b/third_party/xxhash/cli/xsum_os_specific.c new file mode 100644 index 00000000..7c52265d --- /dev/null +++ b/third_party/xxhash/cli/xsum_os_specific.c @@ -0,0 +1,483 @@ +/* + * xxhsum - Command line interface for xxhash algorithms + * Copyright (C) 2013-2021 Yann Collet + * + * GPL v2 License + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can contact the author at: + * - xxHash homepage: https://www.xxhash.com + * - xxHash source repository: https://github.com/Cyan4973/xxHash + */ + +#include "xsum_os_specific.h" /* XSUM_API */ +#include /* stat() / _stat64() */ + +/* + * This file contains all of the ugly boilerplate to make xxhsum work across + * platforms. + */ +#if defined(_MSC_VER) || XSUM_WIN32_USE_WCHAR + typedef struct __stat64 XSUM_stat_t; +# if defined(_MSC_VER) + typedef int mode_t; +# endif +#else + typedef struct stat XSUM_stat_t; +#endif + +#if (defined(__linux__) && (XSUM_PLATFORM_POSIX_VERSION >= 1)) \ + || (XSUM_PLATFORM_POSIX_VERSION >= 200112L) \ + || defined(__DJGPP__) \ + || defined(__MSYS__) \ + || defined(__HAIKU__) +# include /* isatty */ +# define XSUM_IS_CONSOLE(stdStream) isatty(fileno(stdStream)) +#elif defined(MSDOS) || defined(OS2) +# include /* _isatty */ +# define XSUM_IS_CONSOLE(stdStream) _isatty(_fileno(stdStream)) +#elif defined(WIN32) || defined(_WIN32) +# include /* _isatty */ +# include /* DeviceIoControl, HANDLE, FSCTL_SET_SPARSE */ +# include /* FILE */ +static __inline int XSUM_IS_CONSOLE(FILE* stdStream) +{ + DWORD dummy; + return _isatty(_fileno(stdStream)) && GetConsoleMode((HANDLE)_get_osfhandle(_fileno(stdStream)), &dummy); +} +#else +# define XSUM_IS_CONSOLE(stdStream) 0 +#endif + +#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(_WIN32) +# include /* _O_BINARY */ +# include /* _setmode, _fileno, _get_osfhandle */ +# if !defined(__DJGPP__) +# include /* DeviceIoControl, HANDLE, FSCTL_SET_SPARSE */ +# include /* FSCTL_SET_SPARSE */ +# define XSUM_SET_BINARY_MODE(file) { int const unused=_setmode(_fileno(file), _O_BINARY); (void)unused; } +# else +# define XSUM_SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) +# endif +#else +# define XSUM_SET_BINARY_MODE(file) ((void)file) +#endif + +XSUM_API int XSUM_isConsole(FILE* stream) +{ + return XSUM_IS_CONSOLE(stream); +} + +XSUM_API void XSUM_setBinaryMode(FILE* stream) +{ + XSUM_SET_BINARY_MODE(stream); +} + +#if !XSUM_WIN32_USE_WCHAR + +XSUM_API FILE* XSUM_fopen(const char* filename, const char* mode) +{ + return fopen(filename, mode); +} +XSUM_ATTRIBUTE((__format__(__printf__, 2, 0))) +XSUM_API int XSUM_vfprintf(FILE* stream, const char* format, va_list ap) +{ + return vfprintf(stream, format, ap); +} + +static int XSUM_stat(const char* infilename, XSUM_stat_t* statbuf) +{ +#if defined(_MSC_VER) + return _stat64(infilename, statbuf); +#else + return stat(infilename, statbuf); +#endif +} + +#ifndef XSUM_NO_MAIN +int main(int argc, const char* argv[]) +{ + return XSUM_main(argc, argv); +} +#endif + +/* Unicode helpers for Windows to make UTF-8 act as it should. */ +#else +# include +# include + +/***************************************************************************** + * Unicode conversion tools + *****************************************************************************/ + +/* + * Converts a UTF-8 string to UTF-16. Acts like strdup. The string must be freed afterwards. + * This version allows keeping the output length. + */ +static wchar_t* XSUM_widenString(const char* str, int* lenOut) +{ + int const len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + if (lenOut != NULL) *lenOut = len; + if (len == 0) return NULL; + { wchar_t* buf = (wchar_t*)malloc((size_t)len * sizeof(wchar_t)); + if (buf != NULL) { + if (MultiByteToWideChar(CP_UTF8, 0, str, -1, buf, len) == 0) { + free(buf); + return NULL; + } } + return buf; + } +} + +/* + * Converts a UTF-16 string to UTF-8. Acts like strdup. The string must be freed afterwards. + * This version allows keeping the output length. + */ +static char* XSUM_narrowString(const wchar_t *str, int *lenOut) +{ + int len = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); + if (lenOut != NULL) *lenOut = len; + if (len == 0) return NULL; + { char* const buf = (char*)malloc((size_t)len * sizeof(char)); + if (buf != NULL) { + if (WideCharToMultiByte(CP_UTF8, 0, str, -1, buf, len, NULL, NULL) == 0) { + free(buf); + return NULL; + } } + return buf; + } +} + + + +/***************************************************************************** + * File helpers + *****************************************************************************/ +/* + * fopen wrapper that supports UTF-8 + * + * fopen will only accept ANSI filenames, which means that we can't open Unicode filenames. + * + * In order to open a Unicode filename, we need to convert filenames to UTF-16 and use _wfopen. + */ +XSUM_API FILE* XSUM_fopen(const char* filename, const char* mode) +{ + FILE* f = NULL; + wchar_t* const wide_filename = XSUM_widenString(filename, NULL); + if (wide_filename != NULL) { + wchar_t* const wide_mode = XSUM_widenString(mode, NULL); + if (wide_mode != NULL) { + f = _wfopen(wide_filename, wide_mode); + free(wide_mode); + } + free(wide_filename); + } + return f; +} + +/* + * stat() wrapper which supports UTF-8 filenames. + */ +static int XSUM_stat(const char* infilename, XSUM_stat_t* statbuf) +{ + int r = -1; + wchar_t* const wide_filename = XSUM_widenString(infilename, NULL); + if (wide_filename != NULL) { + r = _wstat64(wide_filename, statbuf); + free(wide_filename); + } + return r; +} + +/* + * In case it isn't available, this is what MSVC 2019 defines in stdarg.h. + */ +#if defined(_MSC_VER) && !defined(__clang__) && !defined(va_copy) +# define XSUM_va_copy(destination, source) ((destination) = (source)) +#else +# define XSUM_va_copy(destination, source) va_copy(destination, source) +#endif + +/* + * vasprintf for Windows. + */ +XSUM_ATTRIBUTE((__format__(__printf__, 2, 0))) +static int XSUM_vasprintf(char** strp, const char* format, va_list ap) +{ + int size; + va_list copy; + /* + * To be safe, make a va_copy. + * + * Note that Microsoft doesn't use va_copy in its sample code: + * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/vsprintf-vsprintf-l-vswprintf-vswprintf-l-vswprintf-l?view=vs-2019 + */ + XSUM_va_copy(copy, ap); + /* Calculate how many characters we need */ + size = _vscprintf(format, ap); + va_end(copy); + + if (size < 0) { + *strp = NULL; + return size; + } else { + int ret; + *strp = (char*) malloc((size_t)size + 1); + if (*strp == NULL) { + return -1; + } + /* vsprintf into the new buffer */ + ret = vsprintf(*strp, format, ap); + if (ret < 0) { + free(*strp); + *strp = NULL; + } + return ret; + } +} + +/* + * fprintf wrapper that supports UTF-8. + * + * fprintf doesn't properly handle Unicode on Windows. + * + * Additionally, it is codepage sensitive on console and may crash the program. + * + * Instead, we use vsnprintf, and either print with fwrite or convert to UTF-16 + * for console output and use the codepage-independent WriteConsoleW. + * + * Credit to t-mat: https://github.com/t-mat/xxHash/commit/5691423 + */ +XSUM_ATTRIBUTE((__format__(__printf__, 2, 0))) +XSUM_API int XSUM_vfprintf(FILE *stream, const char *format, va_list ap) +{ + int result; + char* u8_str = NULL; + + /* + * Generate the UTF-8 output string with vasprintf. + */ + result = XSUM_vasprintf(&u8_str, format, ap); + + if (result >= 0) { + const size_t nchar = (size_t)result + 1; + + /* + * Check if we are outputting to a console. Don't use XSUM_isConsole + * directly -- we don't need to call _get_osfhandle twice. + */ + int fileNb = _fileno(stream); + intptr_t handle_raw = _get_osfhandle(fileNb); + HANDLE handle = (HANDLE)handle_raw; + DWORD dwTemp; + + if (handle_raw < 0) { + result = -1; + } else if (_isatty(fileNb) && GetConsoleMode(handle, &dwTemp)) { + /* + * Convert to UTF-16 and output with WriteConsoleW. + * + * This is codepage independent and works on Windows XP's default + * msvcrt.dll. + */ + int len; + wchar_t* const u16_buf = XSUM_widenString(u8_str, &len); + if (u16_buf == NULL) { + result = -1; + } else { + if (WriteConsoleW(handle, u16_buf, (DWORD)len - 1, &dwTemp, NULL)) { + result = (int)dwTemp; + } else { + result = -1; + } + free(u16_buf); + } + } else { + /* fwrite the UTF-8 string if we are printing to a file */ + result = (int)fwrite(u8_str, 1, nchar - 1, stream); + if (result == 0) { + result = -1; + } + } + free(u8_str); + } + return result; +} + +#ifndef XSUM_NO_MAIN +/***************************************************************************** + * Command Line argument parsing + *****************************************************************************/ + +/* Converts a UTF-16 argv to UTF-8. */ +static char** XSUM_convertArgv(int argc, wchar_t* utf16_argv[]) +{ + char** const utf8_argv = (char**)malloc((size_t)(argc + 1) * sizeof(char*)); + if (utf8_argv != NULL) { + int i; + for (i = 0; i < argc; i++) { + utf8_argv[i] = XSUM_narrowString(utf16_argv[i], NULL); + if (utf8_argv[i] == NULL) { + /* Out of memory, whoops. */ + while (i-- > 0) { + free(utf8_argv[i]); + } + free(utf8_argv); + return NULL; + } + } + utf8_argv[argc] = NULL; + } + return utf8_argv; +} +/* Frees arguments returned by XSUM_convertArgv */ +static void XSUM_freeArgv(int argc, char** argv) +{ + int i; + if (argv == NULL) { + return; + } + for (i = 0; i < argc; i++) { + free(argv[i]); + } + free(argv); +} + +static int XSUM_wmain(int argc, wchar_t* utf16_argv[]) +{ + /* Convert the UTF-16 arguments to UTF-8. */ + char** utf8_argv = XSUM_convertArgv(argc, utf16_argv); + + if (utf8_argv == NULL) { + /* An unfortunate but incredibly unlikely error. */ + fprintf(stderr, "xxhsum: error converting command line arguments!\n"); + abort(); + } else { + int ret; + + /* + * MinGW's terminal uses full block buffering for stderr. + * + * This is nonstandard behavior and causes text to not display until + * the buffer fills. + * + * `setvbuf()` can easily correct this to make text display instantly. + */ + setvbuf(stderr, NULL, _IONBF, 0); + + /* Call our real main function */ + ret = XSUM_main(argc, (void*)utf8_argv); + + /* Cleanup */ + XSUM_freeArgv(argc, utf8_argv); + return ret; + } +} + +#if XSUM_WIN32_USE_WMAIN + +/* + * The preferred method of obtaining the real UTF-16 arguments. Always works + * on MSVC, sometimes works on MinGW-w64 depending on the compiler flags. + */ +#ifdef __cplusplus +extern "C" +#endif +int __cdecl wmain(int argc, wchar_t* utf16_argv[]) +{ + return XSUM_wmain(argc, utf16_argv); +} +#else /* !XSUM_WIN32_USE_WMAIN */ + +/* + * Wrap `XSUM_wmain()` using `main()` and `__wgetmainargs()` on MinGW without + * Unicode support. + * + * `__wgetmainargs()` is used in the CRT startup to retrieve the arguments for + * `wmain()`, so we use it on MinGW to emulate `wmain()`. + * + * It is an internal function and not declared in any public headers, so we + * have to declare it manually. + * + * An alternative that doesn't mess with internal APIs is `GetCommandLineW()` + * with `CommandLineToArgvW()`, but the former doesn't expand wildcards and the + * latter requires linking to Shell32.dll and its numerous dependencies. + * + * This method keeps our dependencies to kernel32.dll and the CRT. + * + * https://docs.microsoft.com/en-us/cpp/c-runtime-library/getmainargs-wgetmainargs?view=vs-2019 + */ +typedef struct { + int newmode; +} _startupinfo; + +#ifdef __cplusplus +extern "C" +#endif +int __cdecl __wgetmainargs( + int* Argc, + wchar_t*** Argv, + wchar_t*** Env, + int DoWildCard, + _startupinfo* StartInfo +); + +int main(int ansi_argc, const char* ansi_argv[]) +{ + int utf16_argc; + wchar_t** utf16_argv; + wchar_t** utf16_envp; /* Unused but required */ + _startupinfo startinfo = {0}; /* 0 == don't change new mode */ + + /* Get wmain's UTF-16 arguments. Make sure we expand wildcards. */ + if (__wgetmainargs(&utf16_argc, &utf16_argv, &utf16_envp, 1, &startinfo) < 0) + /* In the very unlikely case of an error, use the ANSI arguments. */ + return XSUM_main(ansi_argc, ansi_argv); + + /* Call XSUM_wmain with our UTF-16 arguments */ + return XSUM_wmain(utf16_argc, utf16_argv); +} + +#endif /* !XSUM_WIN32_USE_WMAIN */ +#endif /* !XSUM_NO_MAIN */ +#endif /* XSUM_WIN32_USE_WCHAR */ + + +/* + * Determines whether the file at filename is a directory. + */ +XSUM_API int XSUM_isDirectory(const char* filename) +{ + XSUM_stat_t statbuf; + int r = XSUM_stat(filename, &statbuf); +#ifdef _MSC_VER + if (!r && (statbuf.st_mode & _S_IFDIR)) return 1; +#else + if (!r && S_ISDIR(statbuf.st_mode)) return 1; +#endif + return 0; +} + +/* + * Returns the filesize of the file at filename. + */ +XSUM_API XSUM_U64 XSUM_getFileSize(const char* filename) +{ + XSUM_stat_t statbuf; + int r = XSUM_stat(filename, &statbuf); + if (r || !S_ISREG(statbuf.st_mode)) return 0; /* No good... */ + return (XSUM_U64)statbuf.st_size; +} diff --git a/third_party/xxhash/cli/xsum_os_specific.h b/third_party/xxhash/cli/xsum_os_specific.h new file mode 100644 index 00000000..e1e080a4 --- /dev/null +++ b/third_party/xxhash/cli/xsum_os_specific.h @@ -0,0 +1,89 @@ +/* + * xxhsum - Command line interface for xxhash algorithms + * Copyright (C) 2013-2021 Yann Collet + * + * GPL v2 License + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can contact the author at: + * - xxHash homepage: https://www.xxhash.com + * - xxHash source repository: https://github.com/Cyan4973/xxHash + */ + +#ifndef XSUM_OS_SPECIFIC_H +#define XSUM_OS_SPECIFIC_H + +#include "xsum_config.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Declared here to be implemented in user code. + * + * Functions like main(), but is passed UTF-8 arguments even on Windows. + */ +XSUM_API int XSUM_main(int argc, const char* argv[]); + +/* + * Returns whether stream is a console. + * + * Functionally equivalent to isatty(fileno(stream)). + */ +XSUM_API int XSUM_isConsole(FILE* stream); + +/* + * Sets stream to pure binary mode (a.k.a. no CRLF conversions). + */ +XSUM_API void XSUM_setBinaryMode(FILE* stream); + +/* + * Returns whether the file at filename is a directory. + */ +XSUM_API int XSUM_isDirectory(const char* filename); + +/* + * Returns the file size of the file at filename. + */ +XSUM_API XSUM_U64 XSUM_getFileSize(const char* filename); + +/* + * UTF-8 stdio wrappers primarily for Windows + */ + +/* + * fopen() wrapper. Accepts UTF-8 filenames on Windows. + * + * Specifically, on Windows, the arguments will be converted to UTF-16 + * and passed to _wfopen(). + */ +XSUM_API FILE* XSUM_fopen(const char* filename, const char* mode); + +/* + * vfprintf() wrapper which prints UTF-8 strings to Windows consoles + * if applicable. + */ +XSUM_ATTRIBUTE((__format__(__printf__, 2, 0))) +XSUM_API int XSUM_vfprintf(FILE* stream, const char* format, va_list ap); + +#ifdef __cplusplus +} +#endif + +#endif /* XSUM_OS_SPECIFIC_H */ diff --git a/third_party/xxhash/cli/xsum_output.c b/third_party/xxhash/cli/xsum_output.c new file mode 100644 index 00000000..ee7002bd --- /dev/null +++ b/third_party/xxhash/cli/xsum_output.c @@ -0,0 +1,66 @@ +/* + * xxhsum - Command line interface for xxhash algorithms + * Copyright (C) 2013-2021 Yann Collet + * + * GPL v2 License + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can contact the author at: + * - xxHash homepage: https://www.xxhash.com + * - xxHash source repository: https://github.com/Cyan4973/xxHash + */ + +#include "xsum_output.h" +#include "xsum_os_specific.h" /* XSUM_API */ + +int XSUM_logLevel = 2; + +XSUM_ATTRIBUTE((__format__(__printf__, 1, 2))) +XSUM_API int XSUM_log(const char* format, ...) +{ + int ret; + va_list ap; + va_start(ap, format); + ret = XSUM_vfprintf(stderr, format, ap); + va_end(ap); + return ret; +} + + +XSUM_ATTRIBUTE((__format__(__printf__, 1, 2))) +XSUM_API int XSUM_output(const char* format, ...) +{ + int ret; + va_list ap; + va_start(ap, format); + ret = XSUM_vfprintf(stdout, format, ap); + va_end(ap); + return ret; +} + +XSUM_ATTRIBUTE((__format__(__printf__, 2, 3))) +XSUM_API int XSUM_logVerbose(int minLevel, const char* format, ...) +{ + if (XSUM_logLevel >= minLevel) { + int ret; + va_list ap; + va_start(ap, format); + ret = XSUM_vfprintf(stderr, format, ap); + va_end(ap); + return ret; + } + return 0; +} diff --git a/third_party/xxhash/cli/xsum_output.h b/third_party/xxhash/cli/xsum_output.h new file mode 100644 index 00000000..9a7926df --- /dev/null +++ b/third_party/xxhash/cli/xsum_output.h @@ -0,0 +1,62 @@ +/* + * xxhsum - Command line interface for xxhash algorithms + * Copyright (C) 2013-2021 Yann Collet + * + * GPL v2 License + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can contact the author at: + * - xxHash homepage: https://www.xxhash.com + * - xxHash source repository: https://github.com/Cyan4973/xxHash + */ + +#ifndef XSUM_OUTPUT_H +#define XSUM_OUTPUT_H + +#include "xsum_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * How verbose the output is. + */ +extern int XSUM_logLevel; + +/* + * Same as fprintf(stderr, format, ...) + */ +XSUM_ATTRIBUTE((__format__(__printf__, 1, 2))) +XSUM_API int XSUM_log(const char *format, ...); + +/* + * Like XSUM_log, but only outputs if XSUM_logLevel >= minLevel. + */ +XSUM_ATTRIBUTE((__format__(__printf__, 2, 3))) +XSUM_API int XSUM_logVerbose(int minLevel, const char *format, ...); + +/* + * Same as printf(format, ...) + */ +XSUM_ATTRIBUTE((__format__(__printf__, 1, 2))) +XSUM_API int XSUM_output(const char *format, ...); + +#ifdef __cplusplus +} +#endif + +#endif /* XSUM_OUTPUT_H */ diff --git a/third_party/xxhash/cli/xsum_sanity_check.c b/third_party/xxhash/cli/xsum_sanity_check.c new file mode 100644 index 00000000..624b93a4 --- /dev/null +++ b/third_party/xxhash/cli/xsum_sanity_check.c @@ -0,0 +1,694 @@ +/* + * xxhsum - Command line interface for xxhash algorithms + * Copyright (C) 2013-2021 Yann Collet + * + * GPL v2 License + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can contact the author at: + * - xxHash homepage: https://www.xxhash.com + * - xxHash source repository: https://github.com/Cyan4973/xxHash + */ + +#include "xsum_sanity_check.h" +#include "xsum_output.h" /* XSUM_log */ +#ifndef XXH_STATIC_LINKING_ONLY +# define XXH_STATIC_LINKING_ONLY +#endif +#include "../xxhash.h" + +#include /* exit */ +#include +#include /* memcmp */ + +/* use #define to make them constant, required for initialization */ +#define PRIME32 2654435761U +#define PRIME64 11400714785074694797ULL + +/* + * Fills a test buffer with pseudorandom data. + * + * This is used in the sanity check - its values must not be changed. + */ +XSUM_API void XSUM_fillTestBuffer(XSUM_U8* buffer, size_t len) +{ + XSUM_U64 byteGen = PRIME32; + size_t i; + + assert(buffer != NULL); + + for (i=0; i>56); + byteGen *= PRIME64; + } +} + + + +/* ************************************************ + * Self-test: + * ensure results consistency across platforms + *********************************************** */ +#if XSUM_NO_TESTS +XSUM_API void XSUM_sanityCheck(void) +{ + XSUM_log("This version of xxhsum is not verified.\n"); +} +#else + +/* + * Test data vectors + */ +typedef struct { + XSUM_U32 len; + XSUM_U32 seed; + XSUM_U32 Nresult; +} XSUM_testdata32_t; + +typedef struct { + XSUM_U32 len; + XSUM_U64 seed; + XSUM_U64 Nresult; +} XSUM_testdata64_t; + +typedef struct { + XSUM_U32 len; + XSUM_U64 seed; + XXH128_hash_t Nresult; +} XSUM_testdata128_t; + +#define SECRET_SAMPLE_NBBYTES 5 +typedef struct { + XSUM_U32 seedLen; + XSUM_U32 secretLen; + XSUM_U8 byte[SECRET_SAMPLE_NBBYTES]; +} XSUM_testdata_sample_t; + +/* XXH32 */ +static const XSUM_testdata32_t XSUM_XXH32_testdata[] = { + { 0, 0, 0x02CC5D05U }, + { 0, PRIME32, 0x36B78AE7U }, + { 1, 0, 0xCF65B03EU }, + { 1, PRIME32, 0xB4545AA4U }, + { 14, 0, 0x1208E7E2U }, + { 14, PRIME32, 0x6AF1D1FEU }, + { 222, 0, 0x5BD11DBDU }, + { 222, PRIME32, 0x58803C5FU } +}; + +/* XXH64 */ +static const XSUM_testdata64_t XSUM_XXH64_testdata[] = { + { 0, 0, 0xEF46DB3751D8E999ULL }, + { 0, PRIME32, 0xAC75FDA2929B17EFULL }, + { 1, 0, 0xE934A84ADB052768ULL }, + { 1, PRIME32, 0x5014607643A9B4C3ULL }, + { 4, 0, 0x9136A0DCA57457EEULL }, + { 14, 0, 0x8282DCC4994E35C8ULL }, + { 14, PRIME32, 0xC3BD6BF63DEB6DF0ULL }, + { 222, 0, 0xB641AE8CB691C174ULL }, + { 222, PRIME32, 0x20CB8AB7AE10C14AULL } +}; +/* + * XXH3: + * Due to being a more complex hash function with specializations for certain + * lengths, a more extensive test is used for XXH3. + */ + +/* XXH3_64bits, seeded */ +static const XSUM_testdata64_t XSUM_XXH3_testdata[] = { + { 0, 0, 0x2D06800538D394C2ULL }, /* empty string */ + { 0, PRIME64, 0xA8A6B918B2F0364AULL }, + { 1, 0, 0xC44BDFF4074EECDBULL }, /* 1 - 3 */ + { 1, PRIME64, 0x032BE332DD766EF8ULL }, + { 6, 0, 0x27B56A84CD2D7325ULL }, /* 4 - 8 */ + { 6, PRIME64, 0x84589C116AB59AB9ULL }, + { 12, 0, 0xA713DAF0DFBB77E7ULL }, /* 9 - 16 */ + { 12, PRIME64, 0xE7303E1B2336DE0EULL }, + { 24, 0, 0xA3FE70BF9D3510EBULL }, /* 17 - 32 */ + { 24, PRIME64, 0x850E80FC35BDD690ULL }, + { 48, 0, 0x397DA259ECBA1F11ULL }, /* 33 - 64 */ + { 48, PRIME64, 0xADC2CBAA44ACC616ULL }, + { 80, 0, 0xBCDEFBBB2C47C90AULL }, /* 65 - 96 */ + { 80, PRIME64, 0xC6DD0CB699532E73ULL }, + { 195, 0, 0xCD94217EE362EC3AULL }, /* 129-240 */ + { 195, PRIME64, 0xBA68003D370CB3D9ULL }, + + { 403, 0, 0xCDEB804D65C6DEA4ULL }, /* one block, last stripe is overlapping */ + { 403, PRIME64, 0x6259F6ECFD6443FDULL }, + { 512, 0, 0x617E49599013CB6BULL }, /* one block, finishing at stripe boundary */ + { 512, PRIME64, 0x3CE457DE14C27708ULL }, + { 2048, 0, 0xDD59E2C3A5F038E0ULL }, /* 2 blocks, finishing at block boundary */ + { 2048, PRIME64, 0x66F81670669ABABCULL }, + { 2099, 0, 0xC6B9D9B3FC9AC765ULL }, /* 2 blocks + 1 partial block, to detect off-by-one scrambling issues, like #816 */ + { 2099, PRIME64, 0x184F316843663974ULL }, + { 2240, 0, 0x6E73A90539CF2948ULL }, /* 3 blocks, finishing at stripe boundary */ + { 2240, PRIME64, 0x757BA8487D1B5247ULL }, + { 2367, 0, 0xCB37AEB9E5D361EDULL }, /* 3 blocks, last stripe is overlapping */ + { 2367, PRIME64, 0xD2DB3415B942B42AULL } +}; +/* XXH3_64bits, custom secret */ +static const XSUM_testdata64_t XSUM_XXH3_withSecret_testdata[] = { + { 0, 0, 0x3559D64878C5C66CULL }, /* empty string */ + { 1, 0, 0x8A52451418B2DA4DULL }, /* 1 - 3 */ + { 6, 0, 0x82C90AB0519369ADULL }, /* 4 - 8 */ + { 12, 0, 0x14631E773B78EC57ULL }, /* 9 - 16 */ + { 24, 0, 0xCDD5542E4A9D9FE8ULL }, /* 17 - 32 */ + { 48, 0, 0x33ABD54D094B2534ULL }, /* 33 - 64 */ + { 80, 0, 0xE687BA1684965297ULL }, /* 65 - 96 */ + { 195, 0, 0xA057273F5EECFB20ULL }, /* 129-240 */ + + { 403, 0, 0x14546019124D43B8ULL }, /* one block, last stripe is overlapping */ + { 512, 0, 0x7564693DD526E28DULL }, /* one block, finishing at stripe boundary */ + { 2048, 0, 0xD32E975821D6519FULL }, /* >= 2 blodcks, at least one scrambling */ + { 2367, 0, 0x293FA8E5173BB5E7ULL }, /* >= 2 blocks, at least one scrambling, last stripe unaligned */ + + { 64*10*3, 0, 0x751D2EC54BC6038BULL } /* exactly 3 full blocks, not a multiple of 256 */ +}; +/* XXH3_128bits, seeded */ +static const XSUM_testdata128_t XSUM_XXH128_testdata[] = { + { 0, 0, { 0x6001C324468D497FULL, 0x99AA06D3014798D8ULL } }, /* empty string */ + { 0, PRIME32, { 0x5444F7869C671AB0ULL, 0x92220AE55E14AB50ULL } }, + { 1, 0, { 0xC44BDFF4074EECDBULL, 0xA6CD5E9392000F6AULL } }, /* 1 - 3 */ + { 1, PRIME32, { 0xB53D5557E7F76F8DULL, 0x89B99554BA22467CULL } }, + { 6, 0, { 0x3E7039BDDA43CFC6ULL, 0x082AFE0B8162D12AULL } }, /* 4 - 8 */ + { 6, PRIME32, { 0x269D8F70BE98856EULL, 0x5A865B5389ABD2B1ULL } }, + { 12, 0, { 0x061A192713F69AD9ULL, 0x6E3EFD8FC7802B18ULL } }, /* 9 - 16 */ + { 12, PRIME32, { 0x9BE9F9A67F3C7DFBULL, 0xD7E09D518A3405D3ULL } }, + { 24, 0, { 0x1E7044D28B1B901DULL, 0x0CE966E4678D3761ULL } }, /* 17 - 32 */ + { 24, PRIME32, { 0xD7304C54EBAD40A9ULL, 0x3162026714A6A243ULL } }, + { 48, 0, { 0xF942219AED80F67BULL, 0xA002AC4E5478227EULL } }, /* 33 - 64 */ + { 48, PRIME32, { 0x7BA3C3E453A1934EULL, 0x163ADDE36C072295ULL } }, + { 81, 0, { 0x5E8BAFB9F95FB803ULL, 0x4952F58181AB0042ULL } }, /* 65 - 96 */ + { 81, PRIME32, { 0x703FBB3D7A5F755CULL, 0x2724EC7ADC750FB6ULL } }, + { 222, 0, { 0xF1AEBD597CEC6B3AULL, 0x337E09641B948717ULL } }, /* 129-240 */ + { 222, PRIME32, { 0xAE995BB8AF917A8DULL, 0x91820016621E97F1ULL } }, + + { 403, 0, { 0xCDEB804D65C6DEA4ULL, 0x1B6DE21E332DD73DULL } }, /* one block, last stripe is overlapping */ + { 403, PRIME64, { 0x6259F6ECFD6443FDULL, 0xBED311971E0BE8F2ULL } }, + { 512, 0, { 0x617E49599013CB6BULL, 0x18D2D110DCC9BCA1ULL } }, /* one block, finishing at stripe boundary */ + { 512, PRIME64, { 0x3CE457DE14C27708ULL, 0x925D06B8EC5B8040ULL } }, + { 2048, 0, { 0xDD59E2C3A5F038E0ULL, 0xF736557FD47073A5ULL } }, /* 2 blocks, finishing at block boundary */ + { 2048, PRIME32, { 0x230D43F30206260BULL, 0x7FB03F7E7186C3EAULL } }, + { 2240, 0, { 0x6E73A90539CF2948ULL, 0xCCB134FBFA7CE49DULL } }, /* 3 blocks, finishing at stripe boundary */ + { 2240, PRIME32, { 0xED385111126FBA6FULL, 0x50A1FE17B338995FULL } }, + { 2367, 0, { 0xCB37AEB9E5D361EDULL, 0xE89C0F6FF369B427ULL } }, /* 3 blocks, last stripe is overlapping */ + { 2367, PRIME32, { 0x6F5360AE69C2F406ULL, 0xD23AAE4B76C31ECBULL } } +}; + +/* XXH128, custom secret */ +static const XSUM_testdata128_t XSUM_XXH128_withSecret_testdata[] = { + { 0, 0, { 0x005923CCEECBE8AEULL, 0x5F70F4EA232F1D38ULL } }, /* empty string */ + { 1, 0, { 0x8A52451418B2DA4DULL, 0x3A66AF5A9819198EULL } }, /* 1 - 3 */ + { 6, 0, { 0x0B61C8ACA7D4778FULL, 0x376BD91B6432F36DULL } }, /* 4 - 8 */ + { 12, 0, { 0xAF82F6EBA263D7D8ULL, 0x90A3C2D839F57D0FULL } } /* 9 - 16 */ +}; + +#define SECRET_SIZE_MAX 9867 +static const XSUM_testdata_sample_t XSUM_XXH3_generateSecret_testdata[] = { + { 0, 192, { 0xE7, 0x8C, 0x77, 0x77, 0x00 } }, + { 1, 240, { 0x2B, 0x3E, 0xDE, 0xC1, 0x00 } }, + { XXH3_SECRET_SIZE_MIN - 1, 277, { 0xE8, 0x39, 0x6C, 0xCC, 0x7B } }, + { XXH3_SECRET_DEFAULT_SIZE + 500, SECRET_SIZE_MAX, { 0xD6, 0x1C, 0x41, 0x17, 0xB3 } } +}; + +static void XSUM_checkResult32(XXH32_hash_t r1, XXH32_hash_t r2) +{ + static int nbTests = 1; + if (r1!=r2) { + XSUM_log("\rError: 32-bit hash test %i: Internal sanity check failed!\n", nbTests); + XSUM_log("\rGot 0x%08X, expected 0x%08X.\n", (unsigned)r1, (unsigned)r2); + XSUM_log("\rNote: If you modified the hash functions, make sure to either update the values\n" + "or temporarily recompile with XSUM_NO_TESTS=1.\n"); + exit(1); + } + nbTests++; +} + +static void XSUM_checkResult64(XXH64_hash_t r1, XXH64_hash_t r2) +{ + static int nbTests = 1; + if (r1!=r2) { + XSUM_log("\rError: 64-bit hash test %i: Internal sanity check failed!\n", nbTests); + XSUM_log("\rGot 0x%08X%08XULL, expected 0x%08X%08XULL.\n", + (unsigned)(r1>>32), (unsigned)r1, (unsigned)(r2>>32), (unsigned)r2); + XSUM_log("\rNote: If you modified the hash functions, make sure to either update the values\n" + "or temporarily recompile with XSUM_NO_TESTS=1.\n"); + exit(1); + } + nbTests++; +} + +static void XSUM_checkResult128(XXH128_hash_t r1, XXH128_hash_t r2) +{ + static int nbTests = 1; + if ((r1.low64 != r2.low64) || (r1.high64 != r2.high64)) { + XSUM_log("\rError: 128-bit hash test %i: Internal sanity check failed.\n", nbTests); + XSUM_log("\rGot { 0x%08X%08XULL, 0x%08X%08XULL }, expected { 0x%08X%08XULL, 0x%08X%08XULL } \n", + (unsigned)(r1.low64>>32), (unsigned)r1.low64, (unsigned)(r1.high64>>32), (unsigned)r1.high64, + (unsigned)(r2.low64>>32), (unsigned)r2.low64, (unsigned)(r2.high64>>32), (unsigned)r2.high64 ); + XSUM_log("\rNote: If you modified the hash functions, make sure to either update the values\n" + "or temporarily recompile with XSUM_NO_TESTS=1.\n"); + exit(1); + } + nbTests++; +} + + +static void XSUM_testXXH32(const void* data, const XSUM_testdata32_t* testData) +{ + XXH32_state_t *state = XXH32_createState(); + size_t pos; + + size_t len = testData->len; + XSUM_U32 seed = testData->seed; + XSUM_U32 Nresult = testData->Nresult; + + if (len == 0) { + data = NULL; + } else { + assert(data != NULL); + } + + assert(state != NULL); + + XSUM_checkResult32(XXH32(data, len, seed), Nresult); + + (void)XXH32_reset(state, seed); + (void)XXH32_update(state, data, len); + XSUM_checkResult32(XXH32_digest(state), Nresult); + + (void)XXH32_reset(state, seed); + for (pos=0; poslen; + XSUM_U64 seed = testData->seed; + XSUM_U64 Nresult = testData->Nresult; + + if (len == 0) { + data = NULL; + } else { + assert(data != NULL); + } + + assert(state != NULL); + + XSUM_checkResult64(XXH64(data, len, seed), Nresult); + + (void)XXH64_reset(state, seed); + (void)XXH64_update(state, data, len); + XSUM_checkResult64(XXH64_digest(state), Nresult); + + (void)XXH64_reset(state, seed); + for (pos=0; pos> 40); +} + +/* + * Technically, XXH3_64bits_update is identical to XXH3_128bits_update as of + * v0.8.0, but we treat them as separate. + */ +typedef XXH_errorcode (*XSUM_XXH3_update_t)(XXH3_state_t* state, const void* input, size_t length); + +/* + * Runs the passed XXH3_update variant on random lengths. This is to test the + * more complex logic of the update function, catching bugs like this one: + * https://github.com/Cyan4973/xxHash/issues/378 + */ +static void XSUM_XXH3_randomUpdate(XXH3_state_t* state, const void* data, + size_t len, XSUM_XXH3_update_t update_fn) +{ + size_t p = 0; + while (p < len) { + size_t const modulo = len > 2 ? len : 2; + size_t l = (size_t)(XSUM_rand()) % modulo; + if (p + l > len) l = len - p; + (void)update_fn(state, (const char*)data+p, l); + p += l; + } +} + +static void XSUM_testXXH3(const void* data, const XSUM_testdata64_t* testData) +{ + size_t len = testData->len; + XSUM_U64 seed = testData->seed; + XSUM_U64 Nresult = testData->Nresult; + if (len == 0) { + data = NULL; + } else { + assert(data != NULL); + } + { XSUM_U64 const Dresult = XXH3_64bits_withSeed(data, len, seed); + XSUM_checkResult64(Dresult, Nresult); + } + + /* check that the no-seed variant produces same result as seed==0 */ + if (seed == 0) { + XSUM_U64 const Dresult = XXH3_64bits(data, len); + XSUM_checkResult64(Dresult, Nresult); + } + + /* check that the combination of + * XXH3_generateSecret_fromSeed() and XXH3_64bits_withSecretandSeed() + * results in exactly the same hash generation as XXH3_64bits_withSeed() */ + { char secretBuffer[XXH3_SECRET_DEFAULT_SIZE+1]; + char* const secret = secretBuffer + 1; /* intentional unalignment */ + XXH3_generateSecret_fromSeed(secret, seed); + { XSUM_U64 const Dresult = XXH3_64bits_withSecretandSeed(data, len, secret, XXH3_SECRET_DEFAULT_SIZE, seed); + XSUM_checkResult64(Dresult, Nresult); + } } + + /* streaming API test */ + { XXH3_state_t* const state = XXH3_createState(); + assert(state != NULL); + /* single ingestion */ + (void)XXH3_64bits_reset_withSeed(state, seed); + (void)XXH3_64bits_update(state, data, len); + XSUM_checkResult64(XXH3_64bits_digest(state), Nresult); + + /* random ingestion */ + (void)XXH3_64bits_reset_withSeed(state, seed); + XSUM_XXH3_randomUpdate(state, data, len, &XXH3_64bits_update); + XSUM_checkResult64(XXH3_64bits_digest(state), Nresult); + + /* byte by byte ingestion */ + { size_t pos; + (void)XXH3_64bits_reset_withSeed(state, seed); + for (pos=0; poslen; + XSUM_U64 Nresult = testData->Nresult; + + if (len == 0) { + data = NULL; + } else { + assert(data != NULL); + } + { XSUM_U64 const Dresult = XXH3_64bits_withSecret(data, len, secret, secretSize); + XSUM_checkResult64(Dresult, Nresult); + } + + /* check that XXH3_64bits_withSecretandSeed() + * results in exactly the same return value as XXH3_64bits_withSecret() */ + if (len > XXH3_MIDSIZE_MAX) + { XSUM_U64 const Dresult = XXH3_64bits_withSecretandSeed(data, len, secret, secretSize, 0); + XSUM_checkResult64(Dresult, Nresult); + } + + /* streaming API test */ + { XXH3_state_t *state = XXH3_createState(); + assert(state != NULL); + (void)XXH3_64bits_reset_withSecret(state, secret, secretSize); + (void)XXH3_64bits_update(state, data, len); + XSUM_checkResult64(XXH3_64bits_digest(state), Nresult); + + /* random ingestion */ + (void)XXH3_64bits_reset_withSecret(state, secret, secretSize); + XSUM_XXH3_randomUpdate(state, data, len, &XXH3_64bits_update); + XSUM_checkResult64(XXH3_64bits_digest(state), Nresult); + + /* byte by byte ingestion */ + { size_t pos; + (void)XXH3_64bits_reset_withSecret(state, secret, secretSize); + for (pos=0; pos XXH3_MIDSIZE_MAX) { + /* single ingestion */ + (void)XXH3_64bits_reset_withSecretandSeed(state, secret, secretSize, 0); + (void)XXH3_64bits_update(state, data, len); + XSUM_checkResult64(XXH3_64bits_digest(state), Nresult); + } + + XXH3_freeState(state); + } +} + +static void XSUM_testXXH128(const void* data, const XSUM_testdata128_t* testData) +{ + size_t len = (size_t)testData->len; + XSUM_U64 seed = testData->seed; + XXH128_hash_t const Nresult = testData->Nresult; + if (len == 0) { + data = NULL; + } else { + assert(data != NULL); + } + + { XXH128_hash_t const Dresult = XXH3_128bits_withSeed(data, len, seed); + XSUM_checkResult128(Dresult, Nresult); + } + + /* check that XXH128() is identical to XXH3_128bits_withSeed() */ + { XXH128_hash_t const Dresult2 = XXH128(data, len, seed); + XSUM_checkResult128(Dresult2, Nresult); + } + + /* check that the no-seed variant produces same result as seed==0 */ + if (seed == 0) { + XXH128_hash_t const Dresult = XXH3_128bits(data, len); + XSUM_checkResult128(Dresult, Nresult); + } + + /* check that the combination of + * XXH3_generateSecret_fromSeed() and XXH3_128bits_withSecretandSeed() + * results in exactly the same hash generation as XXH3_64bits_withSeed() */ + { char secretBuffer[XXH3_SECRET_DEFAULT_SIZE+1]; + char* const secret = secretBuffer + 1; /* intentional unalignment */ + XXH3_generateSecret_fromSeed(secret, seed); + { XXH128_hash_t const Dresult = XXH3_128bits_withSecretandSeed(data, len, secret, XXH3_SECRET_DEFAULT_SIZE, seed); + XSUM_checkResult128(Dresult, Nresult); + } } + + /* streaming API test */ + { XXH3_state_t *state = XXH3_createState(); + assert(state != NULL); + + /* single ingestion */ + (void)XXH3_128bits_reset_withSeed(state, seed); + (void)XXH3_128bits_update(state, data, len); + XSUM_checkResult128(XXH3_128bits_digest(state), Nresult); + + /* random ingestion */ + (void)XXH3_128bits_reset_withSeed(state, seed); + XSUM_XXH3_randomUpdate(state, data, len, &XXH3_128bits_update); + XSUM_checkResult128(XXH3_128bits_digest(state), Nresult); + + /* byte by byte ingestion */ + { size_t pos; + (void)XXH3_128bits_reset_withSeed(state, seed); + for (pos=0; poslen; + XXH128_hash_t Nresult = testData->Nresult; + if (len == 0) { + data = NULL; + } else { + assert(data != NULL); + } + { XXH128_hash_t const Dresult = XXH3_128bits_withSecret(data, len, secret, secretSize); + XSUM_checkResult128(Dresult, Nresult); + } + + /* check that XXH3_128bits_withSecretandSeed() + * results in exactly the same return value as XXH3_128bits_withSecret() */ + if (len > XXH3_MIDSIZE_MAX) + { XXH128_hash_t const Dresult = XXH3_128bits_withSecretandSeed(data, len, secret, secretSize, 0); + XSUM_checkResult128(Dresult, Nresult); + } + + /* streaming API test */ + { XXH3_state_t* const state = XXH3_createState(); + assert(state != NULL); + (void)XXH3_128bits_reset_withSecret(state, secret, secretSize); + (void)XXH3_128bits_update(state, data, len); + XSUM_checkResult128(XXH3_128bits_digest(state), Nresult); + + /* random ingestion */ + (void)XXH3_128bits_reset_withSecret(state, secret, secretSize); + XSUM_XXH3_randomUpdate(state, data, len, &XXH3_128bits_update); + XSUM_checkResult128(XXH3_128bits_digest(state), Nresult); + + /* byte by byte ingestion */ + { size_t pos; + (void)XXH3_128bits_reset_withSecret(state, secret, secretSize); + for (pos=0; pos XXH3_MIDSIZE_MAX) { + /* single ingestion */ + (void)XXH3_128bits_reset_withSecretandSeed(state, secret, secretSize, 0); + (void)XXH3_128bits_update(state, data, len); + XSUM_checkResult128(XXH3_128bits_digest(state), Nresult); + } + + XXH3_freeState(state); + } +} + +static void XSUM_testSecretGenerator(const void* customSeed, const XSUM_testdata_sample_t* testData) +{ + static int nbTests = 1; + const int sampleIndex[SECRET_SAMPLE_NBBYTES] = { 0, 62, 131, 191, 241 }; /* position of sampled bytes */ + XSUM_U8 secretBuffer[SECRET_SIZE_MAX] = {0}; + XSUM_U8 samples[SECRET_SAMPLE_NBBYTES]; + int i; + + assert(testData->secretLen <= SECRET_SIZE_MAX); + XXH3_generateSecret(secretBuffer, testData->secretLen, customSeed, testData->seedLen); + for (i=0; ibyte, sizeof(testData->byte))) { + XSUM_log("\rError: Secret generation test %i: Internal sanity check failed. \n", nbTests); + XSUM_log("\rGot { 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X }, expected { 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X } \n", + samples[0], samples[1], samples[2], samples[3], samples[4], + testData->byte[0], testData->byte[1], testData->byte[2], testData->byte[3], testData->byte[4] ); + exit(1); + } + nbTests++; +} + +/*! + * XSUM_sanityCheck(): + * Runs a sanity check before the benchmark. + * + * Exits on an incorrect output. + */ +XSUM_API void XSUM_sanityCheck(void) +{ + size_t i; +#define SANITY_BUFFER_SIZE 2367 + XSUM_U8 sanityBuffer[SANITY_BUFFER_SIZE]; + const void* const secret = sanityBuffer + 7; + const size_t secretSize = XXH3_SECRET_SIZE_MIN + 11; + assert(sizeof(sanityBuffer) >= 7 + secretSize); + + XSUM_fillTestBuffer(sanityBuffer, sizeof(sanityBuffer)); + + /* XXH32 */ + for (i = 0; i < (sizeof(XSUM_XXH32_testdata)/sizeof(XSUM_XXH32_testdata[0])); i++) { + XSUM_testXXH32(sanityBuffer, &XSUM_XXH32_testdata[i]); + } + /* XXH64 */ + for (i = 0; i < (sizeof(XSUM_XXH64_testdata)/sizeof(XSUM_XXH64_testdata[0])); i++) { + XSUM_testXXH64(sanityBuffer, &XSUM_XXH64_testdata[i]); + } + /* XXH3_64bits, seeded */ + for (i = 0; i < (sizeof(XSUM_XXH3_testdata)/sizeof(XSUM_XXH3_testdata[0])); i++) { + XSUM_testXXH3(sanityBuffer, &XSUM_XXH3_testdata[i]); + } + /* XXH3_64bits, custom secret */ + for (i = 0; i < (sizeof(XSUM_XXH3_withSecret_testdata)/sizeof(XSUM_XXH3_withSecret_testdata[0])); i++) { + XSUM_testXXH3_withSecret(sanityBuffer, secret, secretSize, &XSUM_XXH3_withSecret_testdata[i]); + } + /* XXH128 */ + for (i = 0; i < (sizeof(XSUM_XXH128_testdata)/sizeof(XSUM_XXH128_testdata[0])); i++) { + XSUM_testXXH128(sanityBuffer, &XSUM_XXH128_testdata[i]); + } + /* XXH128 with custom Secret */ + for (i = 0; i < (sizeof(XSUM_XXH128_withSecret_testdata)/sizeof(XSUM_XXH128_withSecret_testdata[0])); i++) { + XSUM_testXXH128_withSecret(sanityBuffer, secret, secretSize, &XSUM_XXH128_withSecret_testdata[i]); + } + /* secret generator */ + for (i = 0; i < (sizeof(XSUM_XXH3_generateSecret_testdata)/sizeof(XSUM_XXH3_generateSecret_testdata[0])); i++) { + assert(XSUM_XXH3_generateSecret_testdata[i].seedLen <= SANITY_BUFFER_SIZE); + XSUM_testSecretGenerator(sanityBuffer, &XSUM_XXH3_generateSecret_testdata[i]); + } + + XSUM_logVerbose(3, "\r%70s\r", ""); /* Clean display line */ + XSUM_logVerbose(3, "Sanity check -- all tests ok\n"); +} + +#endif /* !XSUM_NO_TESTS */ diff --git a/third_party/xxhash/cli/xsum_sanity_check.h b/third_party/xxhash/cli/xsum_sanity_check.h new file mode 100644 index 00000000..af766dfa --- /dev/null +++ b/third_party/xxhash/cli/xsum_sanity_check.h @@ -0,0 +1,60 @@ +/* + * xxhsum - Command line interface for xxhash algorithms + * Copyright (C) 2013-2021 Yann Collet + * + * GPL v2 License + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can contact the author at: + * - xxHash homepage: https://www.xxhash.com + * - xxHash source repository: https://github.com/Cyan4973/xxHash + */ + +#ifndef XSUM_SANITY_CHECK_H +#define XSUM_SANITY_CHECK_H + +#include "xsum_config.h" /* XSUM_API, XSUM_U8 */ + +#include /* size_t */ + + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Runs a series of self-tests. + * + * Exits if any of these tests fail, printing a message to stderr. + * + * If XSUM_NO_TESTS is defined to non-zero, + * this will instead print a warning if this is called (e.g. via xxhsum -b). + */ +XSUM_API void XSUM_sanityCheck(void); + +/* + * Fills a test buffer with pseudorandom data. + * + * This is used in the sanity check and the benchmarks. + * Its values must not be changed. + */ +XSUM_API void XSUM_fillTestBuffer(XSUM_U8* buffer, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* XSUM_SANITY_CHECK_H */ diff --git a/third_party/xxhash/cli/xxhsum.1 b/third_party/xxhash/cli/xxhsum.1 new file mode 100644 index 00000000..715fc4be --- /dev/null +++ b/third_party/xxhash/cli/xxhsum.1 @@ -0,0 +1,106 @@ +.TH "XXHSUM" "1" "December 2021" "xxhsum 0.8.1" "User Commands" +.SH "NAME" +\fBxxhsum\fR \- print or check xxHash non\-cryptographic checksums +.SH "SYNOPSIS" +\fBxxhsum [