From 46a4c48aec82f44e2ba63a33ad03c04240679c06 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 18 Apr 2026 22:30:32 -0500 Subject: [PATCH 01/17] Add md5 sum --- src/CMakeLists.txt | 1 + src/include/migraphx/md5.hpp | 40 +++++++++ src/md5.cpp | 168 +++++++++++++++++++++++++++++++++++ test/md5.cpp | 165 ++++++++++++++++++++++++++++++++++ 4 files changed, 374 insertions(+) create mode 100644 src/include/migraphx/md5.hpp create mode 100644 src/md5.cpp create mode 100644 test/md5.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a91750e00ac..a11c985a5d6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -94,6 +94,7 @@ add_library(migraphx load_save.cpp logger.cpp make_op.cpp + md5.cpp memory_coloring.cpp module.cpp msgpack.cpp diff --git a/src/include/migraphx/md5.hpp b/src/include/migraphx/md5.hpp new file mode 100644 index 00000000000..760ff34f8a5 --- /dev/null +++ b/src/include/migraphx/md5.hpp @@ -0,0 +1,40 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015-2026 Advanced Micro Devices, Inc. All rights reserved. + * + * 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. + */ +#ifndef MIGRAPHX_GUARD_RTGLIB_MD5_HPP +#define MIGRAPHX_GUARD_RTGLIB_MD5_HPP + +#include +#include +#include + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { + +/// Compute the MD5 digest of a string and return it as a lowercase hex string. +std::string MIGRAPHX_EXPORT md5(const std::string_view& str); + +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx + +#endif diff --git a/src/md5.cpp b/src/md5.cpp new file mode 100644 index 00000000000..9210451e1f0 --- /dev/null +++ b/src/md5.cpp @@ -0,0 +1,168 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015-2026 Advanced Micro Devices, Inc. All rights reserved. + * + * 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. + */ +#include +#include +#include +#include +#include +#include + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { + +namespace { + +using u8 = std::uint8_t; +using u32 = std::uint32_t; +using u64 = std::uint64_t; + +constexpr std::size_t block_size = 64; + +// Per-round shift amounts (RFC 1321 section 3.4). +constexpr std::array shifts = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; + +// sine-derived constants: floor(2^32 * abs(sin(i + 1))), i = 0..63. +constexpr std::array sine_table = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, + 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, + 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, + 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, + 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, + 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, + 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, + 0xeb86d391}; + +constexpr u32 rotate_left(u32 x, u32 n) { return (x << n) | (x >> (32u - n)); } + +void process_block(std::array& state, const u8* block) +{ + std::array m{}; + for(std::size_t i = 0; i < 16; ++i) + { + const std::size_t base = i * 4; + m[i] = u32{block[base]} | (u32{block[base + 1]} << 8u) | + (u32{block[base + 2]} << 16u) | (u32{block[base + 3]} << 24u); + } + + u32 a = state[0]; + u32 b = state[1]; + u32 c = state[2]; + u32 d = state[3]; + + for(u32 i = 0; i < 64; ++i) + { + u32 f = 0; + u32 g = 0; + if(i < 16) + { + f = (b & c) | ((~b) & d); + g = i; + } + else if(i < 32) + { + f = (d & b) | ((~d) & c); + g = (5u * i + 1u) % 16u; + } + else if(i < 48) + { + f = b ^ c ^ d; + g = (3u * i + 5u) % 16u; + } + else + { + f = c ^ (b | (~d)); + g = (7u * i) % 16u; + } + + const u32 temp = d; + d = c; + c = b; + b = b + rotate_left(a + f + sine_table[i] + m[g], shifts[i]); + a = temp; + } + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; +} + +std::string digest_to_hex(const std::array& state) +{ + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + for(u32 word : state) + { + for(u32 i = 0; i < 4; ++i) + { + oss << std::setw(2) << ((word >> (8u * i)) & 0xffu); + } + } + return oss.str(); +} + +} // namespace + +std::string md5(const std::string_view& str) +{ + std::array state = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476}; + + const u64 bit_length = static_cast(str.size()) * 8u; + const std::size_t full_blocks = str.size() / block_size; + const std::size_t remainder = str.size() % block_size; + const auto* data = reinterpret_cast(str.data()); + + for(std::size_t i = 0; i < full_blocks; ++i) + { + process_block(state, data + i * block_size); + } + + // Padding: one 0x80 byte, zero fill, and an 8-byte little-endian bit length + // in the last 8 bytes. This consumes one block if remainder < 56, else two. + std::array tail{}; + std::memcpy(tail.data(), data + full_blocks * block_size, remainder); + tail[remainder] = 0x80; + const std::size_t pad_blocks = (remainder < block_size - 8) ? 1 : 2; + const std::size_t tail_size = pad_blocks * block_size; + for(std::size_t i = 0; i < 8; ++i) + { + tail[tail_size - 8 + i] = static_cast((bit_length >> (8u * i)) & 0xffu); + } + + for(std::size_t i = 0; i < pad_blocks; ++i) + { + process_block(state, tail.data() + i * block_size); + } + + return digest_to_hex(state); +} + +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/test/md5.cpp b/test/md5.cpp new file mode 100644 index 00000000000..d2e3616dddf --- /dev/null +++ b/test/md5.cpp @@ -0,0 +1,165 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015-2026 Advanced Micro Devices, Inc. All rights reserved. + * + * 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. + */ +#include +#include "test.hpp" +#include +#include + +// RFC 1321 test vectors (appendix A.5). +TEST_CASE(md5_rfc_empty) { EXPECT(migraphx::md5("") == "d41d8cd98f00b204e9800998ecf8427e"); } + +TEST_CASE(md5_rfc_a) { EXPECT(migraphx::md5("a") == "0cc175b9c0f1b6a831c399e269772661"); } + +TEST_CASE(md5_rfc_abc) { EXPECT(migraphx::md5("abc") == "900150983cd24fb0d6963f7d28e17f72"); } + +TEST_CASE(md5_rfc_message_digest) +{ + EXPECT(migraphx::md5("message digest") == "f96b697d7cb7938d525a2f31aaf161d0"); +} + +TEST_CASE(md5_rfc_lowercase_alphabet) +{ + EXPECT(migraphx::md5("abcdefghijklmnopqrstuvwxyz") == "c3fcd3d76192e4007dfb496cca67e13b"); +} + +TEST_CASE(md5_rfc_alphanumeric) +{ + EXPECT(migraphx::md5("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") == + "d174ab98d277d9f5a5611c2c9f419d9f"); +} + +TEST_CASE(md5_rfc_digits) +{ + EXPECT(migraphx::md5("1234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890") == + "57edf4a22be3c955ac49da2e2107b67a"); +} + +// Well-known extra test strings. +TEST_CASE(md5_quick_brown_fox) +{ + EXPECT(migraphx::md5("The quick brown fox jumps over the lazy dog") == + "9e107d9d372bb6826bd81d3542a419d6"); +} + +TEST_CASE(md5_quick_brown_fox_period) +{ + EXPECT(migraphx::md5("The quick brown fox jumps over the lazy dog.") == + "e4d909c290d0fb1ca068ffaddf22cbd0"); +} + +// Block boundary tests. MD5 processes in 64-byte blocks; the bit length field +// must fit in the trailing 8 bytes of the last padding block, so input length +// modulo 64 being 55/56/63/64 exercises all padding branches. +TEST_CASE(md5_length_55) +{ + EXPECT(migraphx::md5(std::string(55, 'a')) == "ef1772b6dff9a122358552954ad0df65"); +} + +TEST_CASE(md5_length_56) +{ + EXPECT(migraphx::md5(std::string(56, 'a')) == "3b0c8ac703f828b04c6c197006d17218"); +} + +TEST_CASE(md5_length_63) +{ + EXPECT(migraphx::md5(std::string(63, 'a')) == "b06521f39153d618550606be297466d5"); +} + +TEST_CASE(md5_length_64) +{ + EXPECT(migraphx::md5(std::string(64, 'a')) == "014842d480b571495a4a0363793f7367"); +} + +TEST_CASE(md5_length_65) +{ + EXPECT(migraphx::md5(std::string(65, 'a')) == "c743a45e0d2e6a95cb859adae0248435"); +} + +TEST_CASE(md5_length_119) +{ + EXPECT(migraphx::md5(std::string(119, 'a')) == "8a7bd0732ed6a28ce75f6dabc90e1613"); +} + +TEST_CASE(md5_length_120) +{ + EXPECT(migraphx::md5(std::string(120, 'a')) == "5f61c0ccad4cac44c75ff505e1f1e537"); +} + +// Embedded NUL bytes must be hashed, not treated as terminators. +TEST_CASE(md5_embedded_null) +{ + const std::string s{"a\0b", 3}; + EXPECT(migraphx::md5(s) == "70350f6027bce3713f6b76473084309b"); +} + +TEST_CASE(md5_trailing_null) +{ + EXPECT(migraphx::md5(std::string(1, '\0')) == "93b885adfe0da089cdf634904fd59f71"); +} + +// High-byte input — make sure bytes aren't sign-extended. +TEST_CASE(md5_high_bytes) +{ + const std::string s(16, static_cast(0xff)); + EXPECT(migraphx::md5(s) == "8d79cbc9a4ecdde112fc91ba625b13c2"); +} + +// Output shape: 32 lowercase hex characters. +TEST_CASE(md5_output_format) +{ + const std::string hash = migraphx::md5("anything"); + EXPECT(hash.size() == 32); + for(char c : hash) + { + const bool is_hex = (c >= '0' and c <= '9') or (c >= 'a' and c <= 'f'); + EXPECT(is_hex); + } +} + +// Determinism: same input yields same digest every call. +TEST_CASE(md5_deterministic) +{ + const std::string input = "repeatable input"; + EXPECT(migraphx::md5(input) == migraphx::md5(input)); +} + +// Small perturbations of input yield different digests (avalanche sanity check). +TEST_CASE(md5_distinct_outputs) +{ + EXPECT(migraphx::md5("abc") != migraphx::md5("abd")); + EXPECT(migraphx::md5("abc") != migraphx::md5("ABC")); + EXPECT(migraphx::md5("") != migraphx::md5(" ")); + EXPECT(migraphx::md5("a") != migraphx::md5("aa")); +} + +// Accepts string_view constructed from char buffer. +TEST_CASE(md5_string_view_from_buffer) +{ + const char buf[] = "abc"; + const std::string_view view = {buf, 3}; + EXPECT(migraphx::md5(view) == "900150983cd24fb0d6963f7d28e17f72"); +} + +int main(int argc, const char* argv[]) { test::run(argc, argv); } From d3704667d22b05a535877fb36fef2e78fe32fb5e Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 19 Apr 2026 17:19:54 -0500 Subject: [PATCH 02/17] Some cleanup --- src/md5.cpp | 103 ++++++++++++++++++++++++++------------------------- test/md5.cpp | 4 +- 2 files changed, 55 insertions(+), 52 deletions(-) diff --git a/src/md5.cpp b/src/md5.cpp index 9210451e1f0..52ec7ed63b9 100644 --- a/src/md5.cpp +++ b/src/md5.cpp @@ -22,11 +22,10 @@ * THE SOFTWARE. */ #include +#include #include #include -#include -#include -#include +#include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { @@ -40,36 +39,37 @@ using u64 = std::uint64_t; constexpr std::size_t block_size = 64; // Per-round shift amounts (RFC 1321 section 3.4). -constexpr std::array shifts = { - 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, - 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, - 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, - 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; +constexpr std::array shifts = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; -// sine-derived constants: floor(2^32 * abs(sin(i + 1))), i = 0..63. +// Sine-derived constants: floor(2^32 * abs(sin(i + 1))), i = 0..63. constexpr std::array sine_table = { - 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, - 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, - 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, - 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, - 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, - 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, - 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, - 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, - 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, - 0xeb86d391}; + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391}; constexpr u32 rotate_left(u32 x, u32 n) { return (x << n) | (x >> (32u - n)); } +constexpr u32 load_le32(const u8* p) +{ + return u32{p[0]} | (u32{p[1]} << 8u) | (u32{p[2]} << 16u) | (u32{p[3]} << 24u); +} + void process_block(std::array& state, const u8* block) { std::array m{}; - for(std::size_t i = 0; i < 16; ++i) - { - const std::size_t base = i * 4; - m[i] = u32{block[base]} | (u32{block[base + 1]} << 8u) | - (u32{block[base + 2]} << 16u) | (u32{block[base + 3]} << 24u); - } + std::generate(m.begin(), m.end(), [p = block]() mutable { + const u32 v = load_le32(p); + p += 4; + return v; + }); u32 a = state[0]; u32 b = state[1]; @@ -101,11 +101,11 @@ void process_block(std::array& state, const u8* block) g = (7u * i) % 16u; } - const u32 temp = d; - d = c; - c = b; - b = b + rotate_left(a + f + sine_table[i] + m[g], shifts[i]); - a = temp; + const u32 new_b = b + rotate_left(a + f + sine_table[i] + m[g], shifts[i]); + a = d; + d = c; + c = b; + b = new_b; } state[0] += a; @@ -116,16 +116,16 @@ void process_block(std::array& state, const u8* block) std::string digest_to_hex(const std::array& state) { - std::ostringstream oss; - oss << std::hex << std::setfill('0'); - for(u32 word : state) + constexpr std::array hex = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + std::string out(32, '0'); + for(std::size_t i = 0; i < 16; ++i) { - for(u32 i = 0; i < 4; ++i) - { - oss << std::setw(2) << ((word >> (8u * i)) & 0xffu); - } + const u8 byte = (state[i / 4] >> ((i % 4) * 8u)) & 0xffu; + out[2 * i] = hex[byte >> 4u]; + out[(2 * i) + 1] = hex[byte & 0x0fu]; } - return oss.str(); + return out; } } // namespace @@ -134,31 +134,34 @@ std::string md5(const std::string_view& str) { std::array state = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476}; - const u64 bit_length = static_cast(str.size()) * 8u; - const std::size_t full_blocks = str.size() / block_size; - const std::size_t remainder = str.size() % block_size; - const auto* data = reinterpret_cast(str.data()); + const auto* data = reinterpret_cast(str.data()); + const std::size_t full_blocks = str.size() / block_size; + const std::size_t remainder = str.size() % block_size; for(std::size_t i = 0; i < full_blocks; ++i) { - process_block(state, data + i * block_size); + process_block(state, data + (i * block_size)); } - // Padding: one 0x80 byte, zero fill, and an 8-byte little-endian bit length - // in the last 8 bytes. This consumes one block if remainder < 56, else two. + // Final block(s): remaining bytes, a 0x80 terminator, zero fill, and the + // message bit length in the last 8 bytes (little-endian). Two blocks are + // needed when the bit-length field no longer fits in the current block. std::array tail{}; - std::memcpy(tail.data(), data + full_blocks * block_size, remainder); - tail[remainder] = 0x80; + std::copy_n(data + (full_blocks * block_size), remainder, tail.begin()); + tail[remainder] = 0x80; + const std::size_t pad_blocks = (remainder < block_size - 8) ? 1 : 2; const std::size_t tail_size = pad_blocks * block_size; - for(std::size_t i = 0; i < 8; ++i) + u64 bit_length = u64{str.size()} * 8u; + for(auto it = tail.begin() + tail_size - 8; it != tail.begin() + tail_size; ++it) { - tail[tail_size - 8 + i] = static_cast((bit_length >> (8u * i)) & 0xffu); + *it = static_cast(bit_length); + bit_length >>= 8u; } for(std::size_t i = 0; i < pad_blocks; ++i) { - process_block(state, tail.data() + i * block_size); + process_block(state, tail.data() + (i * block_size)); } return digest_to_hex(state); diff --git a/test/md5.cpp b/test/md5.cpp index d2e3616dddf..f8d9ac1452c 100644 --- a/test/md5.cpp +++ b/test/md5.cpp @@ -52,7 +52,7 @@ TEST_CASE(md5_rfc_alphanumeric) TEST_CASE(md5_rfc_digits) { EXPECT(migraphx::md5("1234567890123456789012345678901234567890" - "1234567890123456789012345678901234567890") == + "1234567890123456789012345678901234567890") == "57edf4a22be3c955ac49da2e2107b67a"); } @@ -122,7 +122,7 @@ TEST_CASE(md5_trailing_null) // High-byte input — make sure bytes aren't sign-extended. TEST_CASE(md5_high_bytes) { - const std::string s(16, static_cast(0xff)); + const std::string s(16, '\xff'); EXPECT(migraphx::md5(s) == "8d79cbc9a4ecdde112fc91ba625b13c2"); } From 885a7fbda51488731873698c7fc1ed4103f31d76 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 19 Apr 2026 17:34:51 -0500 Subject: [PATCH 03/17] Refactor process_block --- src/md5.cpp | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/md5.cpp b/src/md5.cpp index 52ec7ed63b9..9da09d6450e 100644 --- a/src/md5.cpp +++ b/src/md5.cpp @@ -62,10 +62,11 @@ constexpr u32 load_le32(const u8* p) return u32{p[0]} | (u32{p[1]} << 8u) | (u32{p[2]} << 16u) | (u32{p[3]} << 24u); } -void process_block(std::array& state, const u8* block) +std::array process_block(std::array state, + std::array block) { std::array m{}; - std::generate(m.begin(), m.end(), [p = block]() mutable { + std::generate(m.begin(), m.end(), [p = block.data()]() mutable { const u32 v = load_le32(p); p += 4; return v; @@ -108,10 +109,7 @@ void process_block(std::array& state, const u8* block) b = new_b; } - state[0] += a; - state[1] += b; - state[2] += c; - state[3] += d; + return {state[0] + a, state[1] + b, state[2] + c, state[3] + d}; } std::string digest_to_hex(const std::array& state) @@ -138,30 +136,33 @@ std::string md5(const std::string_view& str) const std::size_t full_blocks = str.size() / block_size; const std::size_t remainder = str.size() % block_size; + std::array block{}; for(std::size_t i = 0; i < full_blocks; ++i) { - process_block(state, data + (i * block_size)); + std::copy_n(data + (i * block_size), block_size, block.begin()); + state = process_block(state, block); } // Final block(s): remaining bytes, a 0x80 terminator, zero fill, and the // message bit length in the last 8 bytes (little-endian). Two blocks are // needed when the bit-length field no longer fits in the current block. - std::array tail{}; - std::copy_n(data + (full_blocks * block_size), remainder, tail.begin()); - tail[remainder] = 0x80; - - const std::size_t pad_blocks = (remainder < block_size - 8) ? 1 : 2; - const std::size_t tail_size = pad_blocks * block_size; - u64 bit_length = u64{str.size()} * 8u; - for(auto it = tail.begin() + tail_size - 8; it != tail.begin() + tail_size; ++it) + std::array, 2> tail{}; + std::copy_n(data + (full_blocks * block_size), remainder, tail[0].begin()); + tail[0][remainder] = 0x80; + + const bool need_two = (remainder >= block_size - 8); + u64 bit_length = u64{str.size()} * 8u; + auto& last = need_two ? tail[1] : tail[0]; + for(auto it = last.end() - 8; it != last.end(); ++it) { *it = static_cast(bit_length); bit_length >>= 8u; } - for(std::size_t i = 0; i < pad_blocks; ++i) + state = process_block(state, tail[0]); + if(need_two) { - process_block(state, tail.data() + (i * block_size)); + state = process_block(state, tail[1]); } return digest_to_hex(state); From 4a6297ae736d581fb31d28056230bb18a78febd2 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 19 Apr 2026 17:34:54 -0500 Subject: [PATCH 04/17] Format --- src/md5.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/md5.cpp b/src/md5.cpp index 9da09d6450e..3a4f327753f 100644 --- a/src/md5.cpp +++ b/src/md5.cpp @@ -62,8 +62,7 @@ constexpr u32 load_le32(const u8* p) return u32{p[0]} | (u32{p[1]} << 8u) | (u32{p[2]} << 16u) | (u32{p[3]} << 24u); } -std::array process_block(std::array state, - std::array block) +std::array process_block(std::array state, std::array block) { std::array m{}; std::generate(m.begin(), m.end(), [p = block.data()]() mutable { From 28e8fb675b759bb82072470893b0fa23d82589c4 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 19 Apr 2026 18:41:35 -0500 Subject: [PATCH 05/17] Use std::rotate --- src/md5.cpp | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/md5.cpp b/src/md5.cpp index 3a4f327753f..dd69e799ad2 100644 --- a/src/md5.cpp +++ b/src/md5.cpp @@ -71,41 +71,34 @@ std::array process_block(std::array state, std::array v = state; + auto& [a, b, c, d] = v; for(u32 i = 0; i < 64; ++i) { - u32 f = 0; - u32 g = 0; + std::array fg{}; if(i < 16) { - f = (b & c) | ((~b) & d); - g = i; + fg = {(b & c) | ((~b) & d), i}; } else if(i < 32) { - f = (d & b) | ((~d) & c); - g = (5u * i + 1u) % 16u; + fg = {(d & b) | ((~d) & c), (5u * i + 1u) % 16u}; } else if(i < 48) { - f = b ^ c ^ d; - g = (3u * i + 5u) % 16u; + fg = {b ^ c ^ d, (3u * i + 5u) % 16u}; } else { - f = c ^ (b | (~d)); - g = (7u * i) % 16u; + fg = {c ^ (b | (~d)), (7u * i) % 16u}; } - const u32 new_b = b + rotate_left(a + f + sine_table[i] + m[g], shifts[i]); - a = d; - d = c; - c = b; - b = new_b; + a = b + rotate_left(a + fg[0] + sine_table[i] + m[fg[1]], shifts[i]); + std::rotate(v.begin(), v.end() - 1, v.end()); } return {state[0] + a, state[1] + b, state[2] + c, state[3] + d}; From a0d1150ad60cd6464283356199fe01d86c1e2529 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 19 Apr 2026 19:39:28 -0500 Subject: [PATCH 06/17] Use to_hex_string --- src/include/migraphx/stringutils.hpp | 38 ++++++++++++++++++++++++++++ src/md5.cpp | 17 ++----------- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/include/migraphx/stringutils.hpp b/src/include/migraphx/stringutils.hpp index fb778aa28b4..d586a4165d5 100644 --- a/src/include/migraphx/stringutils.hpp +++ b/src/include/migraphx/stringutils.hpp @@ -25,12 +25,17 @@ #define MIGRAPHX_GUARD_MIGRAPHLIB_STRINGUTILS_HPP #include +#include +#include #include #include #include +#include #include #include +#include #include +#include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { @@ -236,6 +241,39 @@ inline auto to_hex_float(const T& x) return ss.str(); } +/// Concatenate the lowercase hex representation of each integer in a range. +/// Each element emits 2*sizeof(element) characters. When lsb is false the +/// most-significant byte is emitted first (standard hex notation); when true, +/// the least-significant byte is first (matches byte-stream hash digests). +template +inline std::string to_hex_string(const Range& r, bool lsb = false) +{ + constexpr std::array hex_digits = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + return std::accumulate( + r.begin(), r.end(), std::string{}, [&](std::string acc, const auto& x) { + using T = std::make_unsigned_t>; + const auto u = static_cast(x); + const auto to_byte = [&](std::ptrdiff_t b) -> std::uint8_t { + return (u >> (b * 8u)) & 0xffu; + }; + const auto append_hex = [&](std::string s, std::uint8_t byte) { + s.push_back(hex_digits[byte >> 4u]); + s.push_back(hex_digits[byte & 0x0fu]); + return s; + }; + const auto bytes = range(sizeof(T)); + if(lsb) + { + return transform_accumulate( + bytes.begin(), bytes.end(), std::move(acc), append_hex, to_byte); + } + const auto rbytes = reverse(bytes); + return transform_accumulate( + rbytes.begin(), rbytes.end(), std::move(acc), append_hex, to_byte); + }); +} + } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx diff --git a/src/md5.cpp b/src/md5.cpp index dd69e799ad2..20d7dbfd88b 100644 --- a/src/md5.cpp +++ b/src/md5.cpp @@ -22,6 +22,7 @@ * THE SOFTWARE. */ #include +#include #include #include #include @@ -104,20 +105,6 @@ std::array process_block(std::array state, std::array& state) -{ - constexpr std::array hex = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - std::string out(32, '0'); - for(std::size_t i = 0; i < 16; ++i) - { - const u8 byte = (state[i / 4] >> ((i % 4) * 8u)) & 0xffu; - out[2 * i] = hex[byte >> 4u]; - out[(2 * i) + 1] = hex[byte & 0x0fu]; - } - return out; -} - } // namespace std::string md5(const std::string_view& str) @@ -157,7 +144,7 @@ std::string md5(const std::string_view& str) state = process_block(state, tail[1]); } - return digest_to_hex(state); + return to_hex_string(state, true); } } // namespace MIGRAPHX_INLINE_NS From e48d6c395e7b8578b7e4b6c113f6dfc217bc747b Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 19 Apr 2026 19:39:31 -0500 Subject: [PATCH 07/17] Format --- src/include/migraphx/stringutils.hpp | 41 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/include/migraphx/stringutils.hpp b/src/include/migraphx/stringutils.hpp index d586a4165d5..ca44c34a543 100644 --- a/src/include/migraphx/stringutils.hpp +++ b/src/include/migraphx/stringutils.hpp @@ -250,28 +250,27 @@ inline std::string to_hex_string(const Range& r, bool lsb = false) { constexpr std::array hex_digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - return std::accumulate( - r.begin(), r.end(), std::string{}, [&](std::string acc, const auto& x) { - using T = std::make_unsigned_t>; - const auto u = static_cast(x); - const auto to_byte = [&](std::ptrdiff_t b) -> std::uint8_t { - return (u >> (b * 8u)) & 0xffu; - }; - const auto append_hex = [&](std::string s, std::uint8_t byte) { - s.push_back(hex_digits[byte >> 4u]); - s.push_back(hex_digits[byte & 0x0fu]); - return s; - }; - const auto bytes = range(sizeof(T)); - if(lsb) - { - return transform_accumulate( - bytes.begin(), bytes.end(), std::move(acc), append_hex, to_byte); - } - const auto rbytes = reverse(bytes); + return std::accumulate(r.begin(), r.end(), std::string{}, [&](std::string acc, const auto& x) { + using T = std::make_unsigned_t>; + const auto u = static_cast(x); + const auto to_byte = [&](std::ptrdiff_t b) -> std::uint8_t { + return (u >> (b * 8u)) & 0xffu; + }; + const auto append_hex = [&](std::string s, std::uint8_t byte) { + s.push_back(hex_digits[byte >> 4u]); + s.push_back(hex_digits[byte & 0x0fu]); + return s; + }; + const auto bytes = range(sizeof(T)); + if(lsb) + { return transform_accumulate( - rbytes.begin(), rbytes.end(), std::move(acc), append_hex, to_byte); - }); + bytes.begin(), bytes.end(), std::move(acc), append_hex, to_byte); + } + const auto rbytes = reverse(bytes); + return transform_accumulate( + rbytes.begin(), rbytes.end(), std::move(acc), append_hex, to_byte); + }); } } // namespace MIGRAPHX_INLINE_NS From 07d3810a2649eda969ecbc046fbdc58e36e6a3f1 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 19 Apr 2026 20:13:35 -0500 Subject: [PATCH 08/17] Use partial_sum --- src/md5.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/md5.cpp b/src/md5.cpp index 20d7dbfd88b..ba38001cbd6 100644 --- a/src/md5.cpp +++ b/src/md5.cpp @@ -129,14 +129,16 @@ std::string md5(const std::string_view& str) std::copy_n(data + (full_blocks * block_size), remainder, tail[0].begin()); tail[0][remainder] = 0x80; - const bool need_two = (remainder >= block_size - 8); - u64 bit_length = u64{str.size()} * 8u; - auto& last = need_two ? tail[1] : tail[0]; - for(auto it = last.end() - 8; it != last.end(); ++it) - { - *it = static_cast(bit_length); - bit_length >>= 8u; - } + const bool need_two = (remainder >= block_size - 8); + const u64 bit_length = u64{str.size()} * 8u; + auto& last = need_two ? tail[1] : tail[0]; + const auto bit_indices = range(8); + transform_partial_sum( + bit_indices.begin(), + bit_indices.end(), + last.end() - 8, + [](u64 acc, u64) { return acc >> 8u; }, + [&](auto) { return bit_length; }); state = process_block(state, tail[0]); if(need_two) From fd921330b0656b7c799653f069a023eb2bdf025b Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 20 Apr 2026 09:32:47 -0500 Subject: [PATCH 09/17] Use std::transform --- src/md5.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/md5.cpp b/src/md5.cpp index ba38001cbd6..84c4bcf2b45 100644 --- a/src/md5.cpp +++ b/src/md5.cpp @@ -66,10 +66,9 @@ constexpr u32 load_le32(const u8* p) std::array process_block(std::array state, std::array block) { std::array m{}; - std::generate(m.begin(), m.end(), [p = block.data()]() mutable { - const u32 v = load_le32(p); - p += 4; - return v; + const auto word_indices = range(m.size()); + std::transform(word_indices.begin(), word_indices.end(), m.begin(), [&](std::ptrdiff_t i) { + return load_le32(block.data() + (i * 4)); }); // v holds the round state; after each step v[0] is overwritten with the new From 6f8fff7d8754399ef722b4f6d9238ce9c8fa6865 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 20 Apr 2026 09:55:07 -0500 Subject: [PATCH 10/17] Some cleanup --- src/md5.cpp | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/md5.cpp b/src/md5.cpp index 84c4bcf2b45..c4e686f68a0 100644 --- a/src/md5.cpp +++ b/src/md5.cpp @@ -22,6 +22,7 @@ * THE SOFTWARE. */ #include +#include #include #include #include @@ -33,20 +34,16 @@ inline namespace MIGRAPHX_INLINE_NS { namespace { -using u8 = std::uint8_t; -using u32 = std::uint32_t; -using u64 = std::uint64_t; - constexpr std::size_t block_size = 64; // Per-round shift amounts (RFC 1321 section 3.4). -constexpr std::array shifts = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, +constexpr std::array shifts = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; // Sine-derived constants: floor(2^32 * abs(sin(i + 1))), i = 0..63. -constexpr std::array sine_table = { +constexpr std::array sine_table = { 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, @@ -56,30 +53,32 @@ constexpr std::array sine_table = { 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391}; -constexpr u32 rotate_left(u32 x, u32 n) { return (x << n) | (x >> (32u - n)); } +constexpr std::uint32_t rotate_left(std::uint32_t x, std::uint32_t n) { return (x << n) | (x >> (32u - n)); } -constexpr u32 load_le32(const u8* p) +template +constexpr std::uint32_t load_le32(It p) { - return u32{p[0]} | (u32{p[1]} << 8u) | (u32{p[2]} << 16u) | (u32{p[3]} << 24u); + return std::uint32_t{p[0]} | (std::uint32_t{p[1]} << 8u) | (std::uint32_t{p[2]} << 16u) | (std::uint32_t{p[3]} << 24u); } -std::array process_block(std::array state, std::array block) +std::array process_block(std::array state, + const std::array& block) { - std::array m{}; + std::array m{}; const auto word_indices = range(m.size()); std::transform(word_indices.begin(), word_indices.end(), m.begin(), [&](std::ptrdiff_t i) { - return load_le32(block.data() + (i * 4)); + return load_le32(block.begin() + (i * 4)); }); // v holds the round state; after each step v[0] is overwritten with the new // 'b' and std::rotate shifts the labels so that (a, b, c, d) tracks the // canonical MD5 register carousel (a <- d, b <- new_b, c <- b, d <- c). - std::array v = state; + std::array v = state; auto& [a, b, c, d] = v; - for(u32 i = 0; i < 64; ++i) + for(std::uint32_t i = 0; i < 64; ++i) { - std::array fg{}; + std::array fg{}; if(i < 16) { fg = {(b & c) | ((~b) & d), i}; @@ -104,39 +103,42 @@ std::array process_block(std::array state, std::array(c); } + } // namespace std::string md5(const std::string_view& str) { - std::array state = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476}; + std::array state = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476}; - const auto* data = reinterpret_cast(str.data()); const std::size_t full_blocks = str.size() / block_size; const std::size_t remainder = str.size() % block_size; - std::array block{}; + std::array block{}; for(std::size_t i = 0; i < full_blocks; ++i) { - std::copy_n(data + (i * block_size), block_size, block.begin()); + const auto chunk_begin = str.begin() + (i * block_size); + std::transform(chunk_begin, chunk_begin + block_size, block.begin(), &to_uint8); state = process_block(state, block); } // Final block(s): remaining bytes, a 0x80 terminator, zero fill, and the // message bit length in the last 8 bytes (little-endian). Two blocks are // needed when the bit-length field no longer fits in the current block. - std::array, 2> tail{}; - std::copy_n(data + (full_blocks * block_size), remainder, tail[0].begin()); + std::array, 2> tail{}; + const auto tail_src_begin = str.begin() + (full_blocks * block_size); + std::transform(tail_src_begin, str.end(), tail[0].begin(), &to_uint8); tail[0][remainder] = 0x80; const bool need_two = (remainder >= block_size - 8); - const u64 bit_length = u64{str.size()} * 8u; + const std::uint64_t bit_length = std::uint64_t{str.size()} * 8u; auto& last = need_two ? tail[1] : tail[0]; const auto bit_indices = range(8); transform_partial_sum( bit_indices.begin(), bit_indices.end(), last.end() - 8, - [](u64 acc, u64) { return acc >> 8u; }, + [](std::uint64_t acc, std::uint64_t) { return acc >> 8u; }, [&](auto) { return bit_length; }); state = process_block(state, tail[0]); From 48561dadce7c88abfacb07b1ea5f9f2160ffe6ec Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 20 Apr 2026 09:55:11 -0500 Subject: [PATCH 11/17] Format --- src/md5.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/md5.cpp b/src/md5.cpp index c4e686f68a0..f589b1c3b6f 100644 --- a/src/md5.cpp +++ b/src/md5.cpp @@ -37,10 +37,10 @@ namespace { constexpr std::size_t block_size = 64; // Per-round shift amounts (RFC 1321 section 3.4). -constexpr std::array shifts = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, - 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, - 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, - 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; +constexpr std::array shifts = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, + 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; // Sine-derived constants: floor(2^32 * abs(sin(i + 1))), i = 0..63. constexpr std::array sine_table = { @@ -53,16 +53,20 @@ constexpr std::array sine_table = { 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391}; -constexpr std::uint32_t rotate_left(std::uint32_t x, std::uint32_t n) { return (x << n) | (x >> (32u - n)); } +constexpr std::uint32_t rotate_left(std::uint32_t x, std::uint32_t n) +{ + return (x << n) | (x >> (32u - n)); +} template constexpr std::uint32_t load_le32(It p) { - return std::uint32_t{p[0]} | (std::uint32_t{p[1]} << 8u) | (std::uint32_t{p[2]} << 16u) | (std::uint32_t{p[3]} << 24u); + return std::uint32_t{p[0]} | (std::uint32_t{p[1]} << 8u) | (std::uint32_t{p[2]} << 16u) | + (std::uint32_t{p[3]} << 24u); } std::array process_block(std::array state, - const std::array& block) + const std::array& block) { std::array m{}; const auto word_indices = range(m.size()); @@ -131,7 +135,7 @@ std::string md5(const std::string_view& str) tail[0][remainder] = 0x80; const bool need_two = (remainder >= block_size - 8); - const std::uint64_t bit_length = std::uint64_t{str.size()} * 8u; + const std::uint64_t bit_length = std::uint64_t{str.size()} * 8u; auto& last = need_two ? tail[1] : tail[0]; const auto bit_indices = range(8); transform_partial_sum( From a1cb4c0693a82eabd2f74b09d08e451749b37797 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 20 Apr 2026 10:05:06 -0500 Subject: [PATCH 12/17] Update comment --- src/md5.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/md5.cpp b/src/md5.cpp index f589b1c3b6f..6c91bc84219 100644 --- a/src/md5.cpp +++ b/src/md5.cpp @@ -74,11 +74,12 @@ std::array process_block(std::array state, return load_le32(block.begin() + (i * 4)); }); - // v holds the round state; after each step v[0] is overwritten with the new - // 'b' and std::rotate shifts the labels so that (a, b, c, d) tracks the - // canonical MD5 register carousel (a <- d, b <- new_b, c <- b, d <- c). + // Each round writes the freshly computed value into slot a, then + // std::rotate shifts v right by one so the structured bindings realign + // onto the canonical MD5 register carousel for the next iteration: + // a <- d, b <- a (the just-computed value), c <- b, d <- c. std::array v = state; - auto& [a, b, c, d] = v; + auto& [a, b, c, d] = v; for(std::uint32_t i = 0; i < 64; ++i) { From e60d38b8a6ab2cdc46d234429ab3fa9238e9b4db Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 20 Apr 2026 10:06:17 -0500 Subject: [PATCH 13/17] Pass by value --- src/md5.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/md5.cpp b/src/md5.cpp index 6c91bc84219..6e43026b8ee 100644 --- a/src/md5.cpp +++ b/src/md5.cpp @@ -66,7 +66,7 @@ constexpr std::uint32_t load_le32(It p) } std::array process_block(std::array state, - const std::array& block) + std::array block) { std::array m{}; const auto word_indices = range(m.size()); From fa10a9fcc53705cebaae239c30737fba6be5beb1 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 20 Apr 2026 10:10:49 -0500 Subject: [PATCH 14/17] Add unit tests --- test/stringutils.cpp | 123 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/test/stringutils.cpp b/test/stringutils.cpp index cfb48499ab3..8191fcc2577 100644 --- a/test/stringutils.cpp +++ b/test/stringutils.cpp @@ -23,6 +23,10 @@ */ #include #include +#include +#include +#include +#include TEST_CASE(interpolate_string_simple1) { @@ -124,4 +128,123 @@ TEST_CASE(slit_string_simple3) EXPECT(resuts.front() == "one two three"); } +TEST_CASE(to_hex_string_empty) +{ + const std::vector v{}; + EXPECT(migraphx::to_hex_string(v).empty()); + EXPECT(migraphx::to_hex_string(v, true).empty()); +} + +TEST_CASE(to_hex_string_uint8_single) +{ + const std::vector v{0xab}; + EXPECT(migraphx::to_hex_string(v) == "ab"); + // Single-byte elements are orientation-invariant. + EXPECT(migraphx::to_hex_string(v, true) == "ab"); +} + +TEST_CASE(to_hex_string_uint8_multiple) +{ + const std::vector v{0xca, 0xfe, 0xba, 0xbe}; + EXPECT(migraphx::to_hex_string(v) == "cafebabe"); + EXPECT(migraphx::to_hex_string(v, true) == "cafebabe"); +} + +TEST_CASE(to_hex_string_uint8_zero_and_max) +{ + const std::vector v{0x00, 0xff, 0x01, 0x10}; + EXPECT(migraphx::to_hex_string(v) == "00ff0110"); +} + +TEST_CASE(to_hex_string_uint16_msb) +{ + const std::vector v{0xabcd}; + EXPECT(migraphx::to_hex_string(v) == "abcd"); +} + +TEST_CASE(to_hex_string_uint16_lsb) +{ + const std::vector v{0xabcd}; + EXPECT(migraphx::to_hex_string(v, true) == "cdab"); +} + +TEST_CASE(to_hex_string_uint32_msb) +{ + const std::vector v{0xdeadbeef}; + EXPECT(migraphx::to_hex_string(v) == "deadbeef"); +} + +TEST_CASE(to_hex_string_uint32_lsb) +{ + const std::vector v{0xdeadbeef}; + EXPECT(migraphx::to_hex_string(v, true) == "efbeadde"); +} + +TEST_CASE(to_hex_string_uint64_msb) +{ + const std::vector v{0x0123456789abcdefULL}; + EXPECT(migraphx::to_hex_string(v) == "0123456789abcdef"); +} + +TEST_CASE(to_hex_string_uint64_lsb) +{ + const std::vector v{0x0123456789abcdefULL}; + EXPECT(migraphx::to_hex_string(v, true) == "efcdab8967452301"); +} + +TEST_CASE(to_hex_string_uint32_multiple) +{ + const std::vector v{0xdeadbeef, 0xcafebabe}; + EXPECT(migraphx::to_hex_string(v) == "deadbeefcafebabe"); + EXPECT(migraphx::to_hex_string(v, true) == "efbeaddebebafeca"); +} + +TEST_CASE(to_hex_string_zero_padding) +{ + // Each element produces exactly 2 * sizeof(T) characters, so small values + // are zero-padded to the full element width. + const std::vector v{0x00000001}; + EXPECT(migraphx::to_hex_string(v) == "00000001"); + EXPECT(migraphx::to_hex_string(v, true) == "01000000"); +} + +TEST_CASE(to_hex_string_std_array) +{ + const std::array a = {0x1234, 0x5678}; + EXPECT(migraphx::to_hex_string(a) == "12345678"); + EXPECT(migraphx::to_hex_string(a, true) == "34127856"); +} + +TEST_CASE(to_hex_string_initializer_list) +{ + EXPECT(migraphx::to_hex_string(std::initializer_list{0xde, 0xad}) == "dead"); +} + +TEST_CASE(to_hex_string_length) +{ + // Output length is exactly 2 * sizeof(T) * n regardless of element values. + const std::vector v(5, 0); + EXPECT(migraphx::to_hex_string(v).size() == 2 * sizeof(std::uint32_t) * v.size()); +} + +TEST_CASE(to_hex_string_signed_is_unsigned_bitpattern) +{ + // Signed inputs are reinterpreted through std::make_unsigned, so -1 prints + // as the all-ones byte pattern of its underlying width. + const std::vector v{std::int8_t{-1}}; + EXPECT(migraphx::to_hex_string(v) == "ff"); + + const std::vector w{std::int32_t{-1}}; + EXPECT(migraphx::to_hex_string(w) == "ffffffff"); + EXPECT(migraphx::to_hex_string(w, true) == "ffffffff"); +} + +TEST_CASE(to_hex_string_md5_initial_state_lsb) +{ + // LSB ordering matches MD5's canonical byte layout: the initial state + // serialized LSB-first is the well-known digest of the empty string. + const std::array state = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476}; + EXPECT(migraphx::to_hex_string(state, true) == "0123456789abcdeffedcba9876543210"); +} + int main(int argc, const char* argv[]) { test::run(argc, argv); } From f8884ff4940f7bfbc5e11e50b7ab029c45981c0c Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 23 Apr 2026 10:04:42 -0500 Subject: [PATCH 15/17] Fix tidy and review feedback --- src/include/migraphx/stringutils.hpp | 6 +++--- src/md5.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/include/migraphx/stringutils.hpp b/src/include/migraphx/stringutils.hpp index ca44c34a543..25d64aecaba 100644 --- a/src/include/migraphx/stringutils.hpp +++ b/src/include/migraphx/stringutils.hpp @@ -251,8 +251,8 @@ inline std::string to_hex_string(const Range& r, bool lsb = false) constexpr std::array hex_digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; return std::accumulate(r.begin(), r.end(), std::string{}, [&](std::string acc, const auto& x) { - using T = std::make_unsigned_t>; - const auto u = static_cast(x); + using type = std::make_unsigned_t>; + const auto u = bit_cast(x); const auto to_byte = [&](std::ptrdiff_t b) -> std::uint8_t { return (u >> (b * 8u)) & 0xffu; }; @@ -261,7 +261,7 @@ inline std::string to_hex_string(const Range& r, bool lsb = false) s.push_back(hex_digits[byte & 0x0fu]); return s; }; - const auto bytes = range(sizeof(T)); + const auto bytes = range(sizeof(type)); if(lsb) { return transform_accumulate( diff --git a/src/md5.cpp b/src/md5.cpp index 6e43026b8ee..c4bd5f7ee60 100644 --- a/src/md5.cpp +++ b/src/md5.cpp @@ -108,7 +108,7 @@ std::array process_block(std::array state, return {state[0] + a, state[1] + b, state[2] + c, state[3] + d}; } -std::uint8_t to_uint8(std::int8_t c) { return bit_cast(c); } +std::uint8_t to_uint8(char c) { return bit_cast(c); } } // namespace @@ -137,7 +137,7 @@ std::string md5(const std::string_view& str) const bool need_two = (remainder >= block_size - 8); const std::uint64_t bit_length = std::uint64_t{str.size()} * 8u; - auto& last = need_two ? tail[1] : tail[0]; + const auto& last = need_two ? tail[1] : tail[0]; const auto bit_indices = range(8); transform_partial_sum( bit_indices.begin(), From e2095a0eeeb6bbbb6647d9670ac2d57d6db87436 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 23 Apr 2026 10:04:45 -0500 Subject: [PATCH 16/17] Format --- src/include/migraphx/stringutils.hpp | 2 +- src/md5.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/include/migraphx/stringutils.hpp b/src/include/migraphx/stringutils.hpp index 25d64aecaba..dba49cf2829 100644 --- a/src/include/migraphx/stringutils.hpp +++ b/src/include/migraphx/stringutils.hpp @@ -251,7 +251,7 @@ inline std::string to_hex_string(const Range& r, bool lsb = false) constexpr std::array hex_digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; return std::accumulate(r.begin(), r.end(), std::string{}, [&](std::string acc, const auto& x) { - using type = std::make_unsigned_t>; + using type = std::make_unsigned_t>; const auto u = bit_cast(x); const auto to_byte = [&](std::ptrdiff_t b) -> std::uint8_t { return (u >> (b * 8u)) & 0xffu; diff --git a/src/md5.cpp b/src/md5.cpp index c4bd5f7ee60..639171d9ff8 100644 --- a/src/md5.cpp +++ b/src/md5.cpp @@ -137,7 +137,7 @@ std::string md5(const std::string_view& str) const bool need_two = (remainder >= block_size - 8); const std::uint64_t bit_length = std::uint64_t{str.size()} * 8u; - const auto& last = need_two ? tail[1] : tail[0]; + const auto& last = need_two ? tail[1] : tail[0]; const auto bit_indices = range(8); transform_partial_sum( bit_indices.begin(), From 5b2487f89e557f74050daba742683881fe66dd73 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 23 Apr 2026 10:04:57 -0500 Subject: [PATCH 17/17] Update year --- test/stringutils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/stringutils.cpp b/test/stringutils.cpp index 8191fcc2577..53b46b1dadd 100644 --- a/test/stringutils.cpp +++ b/test/stringutils.cpp @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. + * Copyright (c) 2015-2026 Advanced Micro Devices, Inc. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal